1function model = LQN2QN(lqn)
2% LQN2QN Convert a LayeredNetwork (LQN) to a Network (QN)
using REPLY signals
4% model = LQN2QN(lqn) converts a LayeredNetwork model to an equivalent
5% queueing network that uses REPLY signals to model synchronous call blocking.
7% REPLY Signal Semantics:
8% - Each synchCall creates a Request
class and a Reply signal class
9% - The caller queue blocks after completing service until Reply arrives
10% - The callee processes the request and
class-switches to Reply on completion
11% - Reply signal unblocks the caller and continues downstream
14% Think -> CallerQueue -> CalleeQueue -> CallerQueue (reply) -> Think
15% | blocks |
class switch to Reply | unblocks
18% - Correctly models BLOCKING semantics (server waits
for reply)
19% - Provides per-task queue metrics (queue length, utilization)
20% - Supports multi-tier call chains (A -> B -> C)
21% - Uses DES solver with REPLY signal support (via JAR)
24% lqn = LayeredNetwork(
'MyLQN');
25% % ... define LQN model ...
27% SolverJMT(model).getAvgTable() % or SolverDES via JAR
29% Copyright (c) 2012-2026, Imperial College London
36model = Network([lqn.getName(),
'-QN']);
38%% Identify reference tasks
39refTaskIndices = find(lsn.isref);
40nRefTasks = length(refTaskIndices);
43 line_error(mfilename,
'LQN must have at least one reference task.');
46%% Detect phase-2 activities
47hasPhase2 = isfield(lsn,
'actphase') && ~isempty(lsn.actphase) && any(lsn.actphase > 1);
49%% Build task service demands (split by phase)
50taskServiceByPhase = containers.Map(
'KeyType',
'double',
'ValueType',
'any');
52 tidx = lsn.tshift + t;
53 [ph1Demand, ph2Demand] = getTaskServiceDemandByPhase(lsn, tidx, hasPhase2);
54 taskServiceByPhase(tidx) = [ph1Demand, ph2Demand];
57%% Build call graph:
for each task, find all tasks it calls synchronously
58synchCallsFrom = containers.Map(
'KeyType',
'double',
'ValueType',
'any');
60 tidx = lsn.tshift + t;
61 calls = []; % Array of structs: targetTidx, targetEidx, callMean
63 entries = lsn.entriesof{tidx};
65 if eidx > length(lsn.actsof) || isempty(lsn.actsof{eidx})
68 acts = lsn.actsof{eidx};
70 if lsn.type(aidx) ~= LayeredNetworkElement.ACTIVITY
73 if aidx > length(lsn.callsof) || isempty(lsn.callsof{aidx})
76 callList = lsn.callsof{aidx};
78 if full(lsn.calltype(cidx)) == CallType.SYNC
79 targetEidx = lsn.callpair(cidx, 2);
80 targetTidx = lsn.parent(targetEidx);
81 % Get call mean (number of calls)
83 if isfield(lsn,
'callproc') && ~isempty(lsn.callproc) && ...
84 cidx <= length(lsn.callproc) && isa(lsn.callproc{cidx},
'Distribution')
85 callMean = lsn.callproc{cidx}.getMean();
87 callInfo =
struct(
'targetTidx', targetTidx,
'targetEidx', targetEidx,
'callMean', callMean);
88 calls = [calls, callInfo]; %#ok<AGROW>
93 synchCallsFrom(tidx) = calls;
96%% Find all tasks in call chains starting from reference tasks
99 refTidx = refTaskIndices(rt);
100 tasksInChain = collectTasksInChain(refTidx, synchCallsFrom, tasksInChain);
102tasksInChain = unique(tasksInChain);
104%% Create
nodes for all tasks
105taskQueues = containers.Map(
'KeyType',
'double',
'ValueType',
'any'); % tidx -> Queue/Delay
106thinkNodes = containers.Map(
'KeyType',
'double',
'ValueType',
'any'); % refTidx -> Think Delay
108% Create think
nodes for reference tasks
110 refTidx = refTaskIndices(rt);
111 taskName = lsn.names{refTidx};
112 thinkNode = Delay(model, [taskName,
'_Think']);
113 thinkNodes(refTidx) = thinkNode;
116% Create queues
for all tasks in call chains
117for i = 1:length(tasksInChain)
118 tidx = tasksInChain(i);
119 taskName = lsn.names{tidx};
120 procIdx = lsn.parent(tidx);
121 nServers = lsn.mult(procIdx);
122 sched = lsn.sched(procIdx);
124 if isinf(nServers) || sched == SchedStrategy.INF
125 queue = Delay(model, taskName);
127 queue = Queue(model, taskName, sched);
128 queue.setNumberOfServers(nServers);
130 taskQueues(tidx) = queue;
133%% Create job
classes for each reference task
134% Each ref task gets: Request
class + Reply signal class
135requestClasses = containers.Map(
'KeyType',
'double',
'ValueType',
'any');
136replySignals = containers.Map(
'KeyType',
'double',
'ValueType',
'any');
139 refTidx = refTaskIndices(rt);
140 taskName = lsn.names{refTidx};
141 thinkNode = thinkNodes(refTidx);
142 population = lsn.mult(refTidx);
144 % Create request
class (closed)
145 requestClass = ClosedClass(model, [taskName,
'_Req'], population, thinkNode);
146 requestClasses(refTidx) = requestClass;
148 % Create reply signal
class
149 replySignal = Signal(model, [taskName,
'_Reply'], SignalType.REPLY);
150 replySignal.forJobClass(requestClass);
151 replySignals(refTidx) = replySignal;
153 % IMPORTANT: Override the
default RAND routing that Signal.m sets
154 % We need DISABLED routing so link() can set the proper probabilistic routes
155 for n = 1:length(model.
nodes)
156 if ~isa(model.
nodes{n},
'Sink') % Don
't change sink routing
157 model.nodes{n}.setRouting(replySignal, RoutingStrategy.DISABLED);
162%% Set service times for all nodes and classes
164 refTidx = refTaskIndices(rt);
165 requestClass = requestClasses(refTidx);
166 replySignal = replySignals(refTidx);
167 thinkNode = thinkNodes(refTidx);
169 % Think time at think node
170 thinkDist = lsn.think{refTidx};
171 if isa(thinkDist, 'Immediate
') || thinkDist.getMean() < GlobalConstants.FineTol
172 thinkNode.setService(requestClass, Exp(1e8));
174 thinkNode.setService(requestClass, thinkDist);
176 % Reply passes through think instantly (shouldn't visit, but set
for safety)
177 thinkNode.setService(replySignal, Exp(1e9));
179 % Set service times at all task queues
180 for i = 1:length(tasksInChain)
181 tidx = tasksInChain(i);
182 queue = taskQueues(tidx);
183 phaseDemands = taskServiceByPhase(tidx);
184 serviceMean = phaseDemands(1) + phaseDemands(2); % Total service
186 if serviceMean > GlobalConstants.FineTol
187 queue.setService(requestClass, Exp(1/serviceMean));
189 queue.setService(requestClass, Exp(1e8));
191 % Reply signal passes through instantly (unblocks at caller)
192 queue.setService(replySignal, Exp(1e9));
196%% Build routing matrix with
class switching for REPLY signals
197P = model.initRoutingMatrix();
200 refTidx = refTaskIndices(rt);
201 requestClass = requestClasses(refTidx);
202 replySignal = replySignals(refTidx);
203 thinkNode = thinkNodes(refTidx);
205 % Build the complete call chain from reference task to leaf
206 % callChain = [refTidx, callee1, callee2, ..., leafTidx]
207 callChain = buildCallChain(refTidx, synchCallsFrom);
209 if length(callChain) == 1
210 % No synch calls - just loop Think -> RefQueue -> Think
211 if isKey(taskQueues, refTidx)
212 refQueue = taskQueues(refTidx);
213 P{requestClass, requestClass}(thinkNode, refQueue) = 1.0;
214 P{requestClass, requestClass}(refQueue, thinkNode) = 1.0;
216 P{requestClass, requestClass}(thinkNode, thinkNode) = 1.0;
221 % Build Request path: Think -> task1 -> task2 -> ... -> leafTask
222 % First: Think -> first task in chain
223 firstTidx = callChain(1);
224 if isKey(taskQueues, firstTidx)
225 firstQueue = taskQueues(firstTidx);
226 P{requestClass, requestClass}(thinkNode, firstQueue) = 1.0;
228 % Reference task has no queue (e.g., think-only task)
229 % Skip directly to the first callee
230 if length(callChain) > 1
231 firstQueue = taskQueues(callChain(2));
232 P{requestClass, requestClass}(thinkNode, firstQueue) = 1.0;
233 callChain = callChain(2:end); % Remove ref task from chain
237 % Request path through the call chain
238 for c = 1:(length(callChain) - 1)
239 fromTidx = callChain(c);
240 toTidx = callChain(c + 1);
241 if isKey(taskQueues, fromTidx) && isKey(taskQueues, toTidx)
242 fromQueue = taskQueues(fromTidx);
243 toQueue = taskQueues(toTidx);
244 P{requestClass, requestClass}(fromQueue, toQueue) = 1.0;
248 % Class
switch at the LEAF node (last in chain)
249 % Reply path: leafTask -> ... -> task1 -> Think
250 leafTidx = callChain(end);
251 leafQueue = taskQueues(leafTidx);
253 if length(callChain) == 1
254 % Only one task in chain (the ref task itself)
255 P{requestClass, replySignal}(leafQueue, thinkNode) = 1.0;
257 % Class
switch at leaf, reply flows back through chain
258 prevTidx = callChain(end - 1);
259 prevQueue = taskQueues(prevTidx);
260 P{requestClass, replySignal}(leafQueue, prevQueue) = 1.0;
262 % Reply flows back through intermediate
nodes
263 for c = (length(callChain) - 1):-1:2
264 fromTidx = callChain(c);
265 toTidx = callChain(c - 1);
266 fromQueue = taskQueues(fromTidx);
267 toQueue = taskQueues(toTidx);
268 P{replySignal, replySignal}(fromQueue, toQueue) = 1.0;
271 % Final hop: first task in chain -> Think (
class switch back to Request)
272 % Reply arriving at Think triggers
class-
switch back to Request to complete the cycle
273 firstQueue = taskQueues(callChain(1));
274 P{replySignal, requestClass}(firstQueue, thinkNode) = 1.0;
282%% Helper: Build the call chain from a starting task to the leaf
283function callChain = buildCallChain(startTidx, synchCallsFrom)
284% Returns array of task indices from start to leaf (following first synch call at each level)
286callChain = startTidx;
287currentTidx = startTidx;
290 calls = synchCallsFrom(currentTidx);
292 break; % Reached leaf
294 % Follow the first synch call
295 nextTidx = calls(1).targetTidx;
296 if any(callChain == nextTidx)
297 break; % Avoid cycles
299 callChain = [callChain, nextTidx]; %#ok<AGROW>
300 currentTidx = nextTidx;
305%% Helper: Collects all tasks reachable via synchronous calls
306function tasksInChain = collectTasksInChain(startTidx, synchCallsFrom, tasksInChain)
308if any(tasksInChain == startTidx)
311tasksInChain = [tasksInChain, startTidx];
313calls = synchCallsFrom(startTidx);
315 for i = 1:length(calls)
317 tasksInChain = collectTasksInChain(call.targetTidx, synchCallsFrom, tasksInChain);
323%% Helper: Get service demand
for a task split by phase
324function [ph1Demand, ph2Demand] = getTaskServiceDemandByPhase(lsn, tidx, hasPhase2)
328entries = lsn.entriesof{tidx};
330 if eidx > length(lsn.actsof) || isempty(lsn.actsof{eidx})
333 acts = lsn.actsof{eidx};
335 if lsn.type(aidx) == LayeredNetworkElement.ACTIVITY
336 hostDem = lsn.hostdem{aidx};
338 if isa(hostDem,
'Distribution')
339 demand = hostDem.getMean();
343 a = aidx - lsn.ashift;
344 if a >= 1 && a <= length(lsn.actphase) && lsn.actphase(a) > 1
345 ph2Demand = ph2Demand + demand;
347 ph1Demand = ph1Demand + demand;
350 ph1Demand = ph1Demand + demand;