1function demandEst = infer_mlps(model, node, rt,
class, ql)
2% INFER_MLPS MLPS demand estimation
using sn
struct-level operations.
4% Estimates service demands at a PS queue
using the Maximum Likelihood
5%
for Processor Sharing method. Pre-builds augmented CTMC models
for
6% each unique (tagClass, aQueue) combination once, then uses
7% sn_set_service + solver_ctmc directly inside the optimization loop.
10% model - LINE Network model with delay rates set and queue rates to estimate
11% node - PS queue node (
Station object)
12% rt - response time samples (
column vector, n x 1)
13%
class - class of each sample (
column vector, n x 1)
14% ql - queue lengths at arrival (n x R matrix, per-
class)
17% demandEst - 1 x R vector of estimated mean service demands
19% Copyright (c) 2012-2026, Imperial College London
21% This code
is released under the 3-Clause BSD License.
23sn = model.getStruct();
25nCores = node.getNumberOfServers();
27% Get delay rates from model
30 if sn.sched(i) == SchedStrategy.INF
37 muZ(k) = sn.mu{delayIdx}{k}(1);
40% Initial point estimate
41meanQL = mean(sum(ql, 2));
42Vtilde = min(meanQL, nCores);
45 if ~isempty(rt(
class == j))
46 x0(j) = Vtilde * mean(rt(
class == j)) / meanQL;
53xUB = max(rt) * ones(1, R);
56augMuZ = [muZ, 0]; % placeholder
for tagged
class rate
58% see _kb/03-api-layer.md
for rationale
59uniqueTC = unique(
class);
60uniqueQL = unique(ql,
'rows');
61ctmcOpts = []; % will be set from first solver instance
63% Cached augmented models stored as parallel arrays keyed by state string:
64% prebuiltKeys{i}
is the key, prebuiltVals{i} the corresponding
struct.
67for u = 1:length(uniqueTC)
69 augMuZ(newR) = muZ(tc);
70 for v = 1:size(uniqueQL, 1)
73 % Population: move 1 job from tagClass to tagged class
74 N = aq; N(tc) = N(tc) - 1; N(newR) = 1;
76 % Build augmented Network model (once per combo)
77 augModel = Network('mlps_aug');
79 augNode{1} = Delay(augModel,
'Think');
80 augNode{2} = Queue(augModel,
'Queue1', SchedStrategy.PS);
81 augNode{2}.setNumberOfServers(nCores);
83 augClass = ClosedClass(augModel, sprintf(
'Class%d', r), N(r), augNode{1}, 0);
84 augNode{1}.setService(augClass, Exp(augMuZ(r)));
85 augNode{2}.setService(augClass, Exp(1)); % placeholder
87 P = augModel.initRoutingMatrix;
89 P{r} = Network.serialRouting(augNode);
93 % Use SolverCTMC to get proper options and run initial solve
94 solver = SolverCTMC(augModel);
96 ctmcOpts = solver.getOptions();
98 [infGen, eventFilt, ev] = solver.getGenerator();
99 stateSpaceAggr = solver.getStateSpaceAggr();
100 augSn = augModel.getStruct();
102 % Find tagged departure
event indices (invariant across rate changes)
103 queueNodeIdx = augNode{2}.index;
106 if ev{e}.active{1}.node == queueNodeIdx && ...
107 ev{e}.active{1}.class == newR && ...
108 ev{e}.active{1}.event == EventType.DEP
109 taggedDepIdx(end+1) = e;
113 % Find subset where tagged job
is at Queue (invariant)
114 queueStIdx = augNode{2}.stationIndex;
115 taggedColAtQueue = (queueStIdx - 1) * newR + newR;
116 subset = find(stateSpaceAggr(:, taggedColAtQueue) == 1);
118 % Extract queue state space
for subset (invariant)
119 queueCols = (queueStIdx - 1) * newR + (1:newR);
120 SSqueue = stateSpaceAggr(subset, queueCols);
122 cacheKey = mat2str([tc, aq]);
123 prebuiltKeys{end+1} = cacheKey; %#ok<AGROW>
124 prebuiltVals{end+1} =
struct(...
126 'queueStIdx', queueStIdx, ...
127 'taggedDepIdx', taggedDepIdx, ...
128 'subset', subset, ...
129 'SSqueue', SSqueue, ...
131 'tagClass', tc); %#ok<AGROW>
135%% Optimization options
137options.Display =
'iter';
138options.LargeScale =
'on';
139options.MaxIter = 1e10;
140options.MaxFunEvals = 1e10;
141options.MaxSQPIter = 5000;
142options.TolCon = 1e-6;
143options.Algorithm =
'interior-point';
145[demandEst, ~] = fmincon(@objfun, x0, [], [], [], [], xLB, xUB, [], options);
147 function f = objfun(x)
149 rates = 1 ./ x; % x = mean demands
151 % Build cache: update rates via sn_set_service, call solver_ctmc.
152 % Stored as parallel arrays keyed by state
string.
156 for kk = 1:length(prebuiltKeys)
157 key = prebuiltKeys{kk};
158 pb = prebuiltVals{kk};
160 % Set augmented rates: base classes + tagged
class
161 augRates = [rates, rates(pb.tagClass)];
163 % Update service rates in cached sn
struct (cell-of-cells format)
166 sn_upd = sn_set_service_coc(sn_upd, pb.queueStIdx, cc, augRates(cc));
169 % Run solver_ctmc with updated rates (reuses sn topology/state structure)
170 [infGen, ~, ~, Dfilt] = solver_ctmc(sn_upd, ctmcOpts);
172 % Build D1 from cached departure
event indices
173 D1 = sparse(size(infGen, 1), size(infGen, 2));
174 for di = 1:length(pb.taggedDepIdx)
175 D1 = D1 + Dfilt{pb.taggedDepIdx(di)};
178 % Extract sub-generator
using cached subset
180 A = full(MAPQ1(pb.subset, pb.subset));
182 cacheKeys{end+1} = key; %#ok<AGROW>
183 cacheVals{end+1} =
struct(
'A', A,
'SSqueue', pb.SSqueue,
'N', pb.N); %#ok<AGROW>
186 % Compute likelihoods
using cached CTMCs
187 ftemp = zeros(size(rt));
189 cacheKey = mat2str([
class(r), ql(r, :)]);
190 cached = cacheVals{find(strcmp(cacheKeys, cacheKey), 1)};
191 ftemp(r) = log(TOL + eval_mlps_likelihood(cached.A, cached.SSqueue, cached.N, rt(r)));
199function LIKE = eval_mlps_likelihood(A, SSqueue, N, Rsam)
200% Compute MLPS likelihood from pre-built CTMC components.
202pie = zeros(1, length(A));
203idx = matchrow(SSqueue, N);
204if ~isempty(idx) && idx > 0
208MAP = {A, -A * ones(size(pie))
' * pie};
209LIKE = map_pdf(MAP, Rsam);