LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
LQN2QN.m
1function model = LQN2QN(lqn)
2% LQN2QN Convert a LayeredNetwork (LQN) to a Network (QN) using REPLY signals
3%
4% model = LQN2QN(lqn) converts a LayeredNetwork model to an equivalent
5% queueing network that uses REPLY signals to model synchronous call blocking.
6%
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
12%
13% Network Topology:
14% Think -> CallerQueue -> CalleeQueue -> CallerQueue (reply) -> Think
15% | blocks | class switch to Reply | unblocks
16%
17% This approach:
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)
22%
23% Example:
24% lqn = LayeredNetwork('MyLQN');
25% % ... define LQN model ...
26% model = LQN2QN(lqn);
27% SolverJMT(model).getAvgTable() % or SolverDES via JAR
28%
29% Copyright (c) 2012-2026, Imperial College London
30% All rights reserved.
31
32%% Get LQN structure
33lsn = lqn.getStruct();
34
35%% Create QN model
36model = Network([lqn.getName(), '-QN']);
37
38%% Identify reference tasks
39refTaskIndices = find(lsn.isref);
40nRefTasks = length(refTaskIndices);
41
42if nRefTasks == 0
43 line_error(mfilename, 'LQN must have at least one reference task.');
44end
45
46%% Detect phase-2 activities
47hasPhase2 = isfield(lsn, 'actphase') && ~isempty(lsn.actphase) && any(lsn.actphase > 1);
48
49%% Build task service demands (split by phase)
50taskServiceByPhase = containers.Map('KeyType', 'double', 'ValueType', 'any');
51for t = 1:lsn.ntasks
52 tidx = lsn.tshift + t;
53 [ph1Demand, ph2Demand] = getTaskServiceDemandByPhase(lsn, tidx, hasPhase2);
54 taskServiceByPhase(tidx) = [ph1Demand, ph2Demand];
55end
56
57%% Build call graph: for each task, find all tasks it calls synchronously
58synchCallsFrom = containers.Map('KeyType', 'double', 'ValueType', 'any');
59for t = 1:lsn.ntasks
60 tidx = lsn.tshift + t;
61 calls = []; % Array of structs: targetTidx, targetEidx, callMean
62
63 entries = lsn.entriesof{tidx};
64 for eidx = entries
65 if eidx > length(lsn.actsof) || isempty(lsn.actsof{eidx})
66 continue;
67 end
68 acts = lsn.actsof{eidx};
69 for aidx = acts
70 if lsn.type(aidx) ~= LayeredNetworkElement.ACTIVITY
71 continue;
72 end
73 if aidx > length(lsn.callsof) || isempty(lsn.callsof{aidx})
74 continue;
75 end
76 callList = lsn.callsof{aidx};
77 for cidx = callList
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)
82 callMean = 1.0;
83 if isfield(lsn, 'callproc') && ~isempty(lsn.callproc) && ...
84 cidx <= length(lsn.callproc) && isa(lsn.callproc{cidx}, 'Distribution')
85 callMean = lsn.callproc{cidx}.getMean();
86 end
87 callInfo = struct('targetTidx', targetTidx, 'targetEidx', targetEidx, 'callMean', callMean);
88 calls = [calls, callInfo]; %#ok<AGROW>
89 end
90 end
91 end
92 end
93 synchCallsFrom(tidx) = calls;
94end
95
96%% Find all tasks in call chains starting from reference tasks
97tasksInChain = [];
98for rt = 1:nRefTasks
99 refTidx = refTaskIndices(rt);
100 tasksInChain = collectTasksInChain(refTidx, synchCallsFrom, tasksInChain);
101end
102tasksInChain = unique(tasksInChain);
103
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
107
108% Create think nodes for reference tasks
109for rt = 1:nRefTasks
110 refTidx = refTaskIndices(rt);
111 taskName = lsn.names{refTidx};
112 thinkNode = Delay(model, [taskName, '_Think']);
113 thinkNodes(refTidx) = thinkNode;
114end
115
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);
123
124 if isinf(nServers) || sched == SchedStrategy.INF
125 queue = Delay(model, taskName);
126 else
127 queue = Queue(model, taskName, sched);
128 queue.setNumberOfServers(nServers);
129 end
130 taskQueues(tidx) = queue;
131end
132
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');
137
138for rt = 1:nRefTasks
139 refTidx = refTaskIndices(rt);
140 taskName = lsn.names{refTidx};
141 thinkNode = thinkNodes(refTidx);
142 population = lsn.mult(refTidx);
143
144 % Create request class (closed)
145 requestClass = ClosedClass(model, [taskName, '_Req'], population, thinkNode);
146 requestClasses(refTidx) = requestClass;
147
148 % Create reply signal class
149 replySignal = Signal(model, [taskName, '_Reply'], SignalType.REPLY);
150 replySignal.forJobClass(requestClass);
151 replySignals(refTidx) = replySignal;
152
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);
158 end
159 end
160end
161
162%% Set service times for all nodes and classes
163for rt = 1:nRefTasks
164 refTidx = refTaskIndices(rt);
165 requestClass = requestClasses(refTidx);
166 replySignal = replySignals(refTidx);
167 thinkNode = thinkNodes(refTidx);
168
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));
173 else
174 thinkNode.setService(requestClass, thinkDist);
175 end
176 % Reply passes through think instantly (shouldn't visit, but set for safety)
177 thinkNode.setService(replySignal, Exp(1e9));
178
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
185
186 if serviceMean > GlobalConstants.FineTol
187 queue.setService(requestClass, Exp(1/serviceMean));
188 else
189 queue.setService(requestClass, Exp(1e8));
190 end
191 % Reply signal passes through instantly (unblocks at caller)
192 queue.setService(replySignal, Exp(1e9));
193 end
194end
195
196%% Build routing matrix with class switching for REPLY signals
197P = model.initRoutingMatrix();
198
199for rt = 1:nRefTasks
200 refTidx = refTaskIndices(rt);
201 requestClass = requestClasses(refTidx);
202 replySignal = replySignals(refTidx);
203 thinkNode = thinkNodes(refTidx);
204
205 % Build the complete call chain from reference task to leaf
206 % callChain = [refTidx, callee1, callee2, ..., leafTidx]
207 callChain = buildCallChain(refTidx, synchCallsFrom);
208
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;
215 else
216 P{requestClass, requestClass}(thinkNode, thinkNode) = 1.0;
217 end
218 continue;
219 end
220
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;
227 else
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
234 end
235 end
236
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;
245 end
246 end
247
248 % Class switch at the LEAF node (last in chain)
249 % Reply path: leafTask -> ... -> task1 -> Think
250 leafTidx = callChain(end);
251 leafQueue = taskQueues(leafTidx);
252
253 if length(callChain) == 1
254 % Only one task in chain (the ref task itself)
255 P{requestClass, replySignal}(leafQueue, thinkNode) = 1.0;
256 else
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;
261
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;
269 end
270
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;
275 end
276end
277
278model.link(P);
279
280end
281
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)
285
286callChain = startTidx;
287currentTidx = startTidx;
288
289while true
290 calls = synchCallsFrom(currentTidx);
291 if isempty(calls)
292 break; % Reached leaf
293 end
294 % Follow the first synch call
295 nextTidx = calls(1).targetTidx;
296 if any(callChain == nextTidx)
297 break; % Avoid cycles
298 end
299 callChain = [callChain, nextTidx]; %#ok<AGROW>
300 currentTidx = nextTidx;
301end
302
303end
304
305%% Helper: Collects all tasks reachable via synchronous calls
306function tasksInChain = collectTasksInChain(startTidx, synchCallsFrom, tasksInChain)
307
308if any(tasksInChain == startTidx)
309 return;
310end
311tasksInChain = [tasksInChain, startTidx];
312
313calls = synchCallsFrom(startTidx);
314if ~isempty(calls)
315 for i = 1:length(calls)
316 call = calls(i);
317 tasksInChain = collectTasksInChain(call.targetTidx, synchCallsFrom, tasksInChain);
318 end
319end
320
321end
322
323%% Helper: Get service demand for a task split by phase
324function [ph1Demand, ph2Demand] = getTaskServiceDemandByPhase(lsn, tidx, hasPhase2)
325ph1Demand = 0;
326ph2Demand = 0;
327
328entries = lsn.entriesof{tidx};
329for eidx = entries
330 if eidx > length(lsn.actsof) || isempty(lsn.actsof{eidx})
331 continue;
332 end
333 acts = lsn.actsof{eidx};
334 for aidx = acts
335 if lsn.type(aidx) == LayeredNetworkElement.ACTIVITY
336 hostDem = lsn.hostdem{aidx};
337 demand = 0;
338 if isa(hostDem, 'Distribution')
339 demand = hostDem.getMean();
340 end
341
342 if hasPhase2
343 a = aidx - lsn.ashift;
344 if a >= 1 && a <= length(lsn.actphase) && lsn.actphase(a) > 1
345 ph2Demand = ph2Demand + demand;
346 else
347 ph1Demand = ph1Demand + demand;
348 end
349 else
350 ph1Demand = ph1Demand + demand;
351 end
352 end
353 end
354end
355
356end
Definition mmt.m:92