1function model = LQN2QN(lqn)
2% LQN2QN Convert a LayeredNetwork (LQN) to a Network (QN)
4% model = LQN2QN(lqn) converts a LayeredNetwork model to an equivalent
5% ordinary queueing network that approximates LQN blocking semantics.
7% The conversion models synchronous call blocking by aggregating service
8% times: when a task makes a synchCall, its effective service time includes
9% both its own processing and all downstream processing (since it blocks
10% until the reply
is received).
13% - Correctly models THROUGHPUT (validated against LQNS)
14% - Queue lengths are APPROXIMATED (blocking aggregated into entry task)
16% For accurate queue length distribution across tiers,
explicit blocking
17% with Reply signals would be required (future enhancement).
20% lqn = LayeredNetwork(
'MyLQN');
21% % ... define LQN model ...
23% SolverDES(model).getAvgTable()
25% Copyright (c) 2012-2026, Imperial College London
32model = Network([lqn.getName(),
'-QN']);
34%% Identify reference tasks
35refTaskIndices = find(lsn.isref);
36nRefTasks = length(refTaskIndices);
39 line_error(mfilename,
'LQN must have at least one reference task.');
42%% Build call graph and compute effective service times
43effectiveService = zeros(1, lsn.ntasks + lsn.tshift);
45% First pass: get own service times
47 tidx = lsn.tshift + t;
48 effectiveService(tidx) = getTaskServiceDemand(lsn, tidx);
52downstreamTasks = cell(1, lsn.ntasks + lsn.tshift);
54 tidx = lsn.tshift + t;
55 downstreamTasks{tidx} = [];
57 entries = lsn.entriesof{tidx};
59 acts = lsn.actsof{eidx};
61 if lsn.type(aidx) ~= LayeredNetworkElement.ACTIVITY
64 if aidx > length(lsn.callsof) || isempty(lsn.callsof{aidx})
67 calls = lsn.callsof{aidx};
69 if full(lsn.calltype(cidx)) == CallType.SYNC
70 targetEidx = lsn.callpair(cidx, 2);
71 targetTidx = lsn.parent(targetEidx);
72 downstreamTasks{tidx} = [downstreamTasks{tidx}, targetTidx];
79% Compute effective service times (includes all downstream blocking time)
80computed =
false(1, lsn.ntasks + lsn.tshift);
81for iter = 1:lsn.ntasks
84 tidx = lsn.tshift + t;
89 downstream = downstreamTasks{tidx};
90 if isempty(downstream)
91 computed(tidx) =
true;
94 allDownstreamComputed =
true;
97 allDownstreamComputed =
false;
102 if allDownstreamComputed
104 effectiveService(tidx) = effectiveService(tidx) + effectiveService(dt);
106 computed(tidx) =
true;
117procNodes = cell(lsn.nhosts, 1);
118thinkNodes = cell(lsn.ntasks, 1);
120% Find entry tasks
for reference tasks
121entryTaskForRef = zeros(1, nRefTasks);
123 tidx = refTaskIndices(rt);
124 downstream = downstreamTasks{tidx};
125 if ~isempty(downstream)
126 entryTaskForRef(rt) = downstream(1);
130% Create processor
nodes for entry tasks
132 entryTidx = entryTaskForRef(rt);
137 procIdx = lsn.parent(entryTidx);
138 if isempty(procIdx) || procIdx <= 0 || procIdx > lsn.nhosts || ~isempty(procNodes{procIdx})
142 procName = lsn.names{procIdx};
143 nServers = lsn.mult(procIdx);
144 sched = lsn.sched(procIdx);
146 if isinf(nServers) || sched == SchedStrategy.INF
147 procNodes{procIdx} = Delay(model, procName);
149 procNodes{procIdx} = Queue(model, procName, sched);
150 procNodes{procIdx}.setNumberOfServers(nServers);
156 tidx = refTaskIndices(rt);
157 taskName = lsn.names{tidx};
158 t = tidx - lsn.tshift;
159 thinkNodes{t} = Delay(model, [taskName,
'_Think']);
162%% Create job
classes and set service times
163jobClasses = cell(nRefTasks, 1);
166 tidx = refTaskIndices(rt);
167 taskName = lsn.names{tidx};
168 t = tidx - lsn.tshift;
169 thinkNode = thinkNodes{t};
172 jobClass = ClosedClass(model, [taskName,
'_Job'], N, thinkNode);
173 jobClasses{rt} = jobClass;
176 thinkDist = lsn.think{tidx};
177 if isa(thinkDist,
'Immediate') || thinkDist.getMean() < GlobalConstants.FineTol
178 thinkDist = Exp(1e8);
180 thinkNode.setService(jobClass, thinkDist);
182 % Effective service at entry task
's processor
183 entryTidx = entryTaskForRef(rt);
185 entryProcIdx = lsn.parent(entryTidx);
186 if ~isempty(entryProcIdx) && entryProcIdx > 0 && entryProcIdx <= lsn.nhosts && ~isempty(procNodes{entryProcIdx})
187 procNode = procNodes{entryProcIdx};
188 effService = effectiveService(entryTidx);
190 if effService > GlobalConstants.FineTol
191 procNode.setService(jobClass, Exp(1/effService));
193 procNode.setService(jobClass, Immediate());
199%% Build routing matrix
200P = model.initRoutingMatrix();
203 tidx = refTaskIndices(rt);
204 t = tidx - lsn.tshift;
205 thinkNode = thinkNodes{t};
206 jobClass = jobClasses{rt};
208 entryTidx = entryTaskForRef(rt);
210 P{jobClass, jobClass}(thinkNode, thinkNode) = 1.0;
214 entryProcIdx = lsn.parent(entryTidx);
215 if isempty(entryProcIdx) || entryProcIdx <= 0 || entryProcIdx > lsn.nhosts || isempty(procNodes{entryProcIdx})
216 P{jobClass, jobClass}(thinkNode, thinkNode) = 1.0;
220 procNode = procNodes{entryProcIdx};
221 P{jobClass, jobClass}(thinkNode, procNode) = 1.0;
222 P{jobClass, jobClass}(procNode, thinkNode) = 1.0;
229%% Helper: Get service demand for a task
230function demand = getTaskServiceDemand(lsn, tidx)
233 entries = lsn.entriesof{tidx};
235 acts = lsn.actsof{eidx};
237 if lsn.type(aidx) == LayeredNetworkElement.ACTIVITY
238 hostDem = lsn.hostdem{aidx};
239 if isa(hostDem, 'Distribution
')
240 demand = demand + hostDem.getMean();