1function [estVal,fObjFun] = estimator_mle(self,
nodes)
3% MLE Maximum likelihood demand estimator
5% Uses a LINE solver (
default: SolverAuto) to compute predicted
6% response times and utilizations, replacing the M/GI/1-PS formula.
7% Updates service rates efficiently via sn_set_service.
9% Copyright (c) 2012-2026, Imperial College London
11% This code
is released under the 3-Clause BSD License.
13% This estimator at present can handle estimation on a single resource.
16% rescale utilization to be mean number of busy servers
17sn = self.model.getStruct;
19if isfinite(node.getNumberOfServers())
20 U = self.getAggrUtil(node);
22 avgU = U.data * node.getNumberOfServers();
26% obtain per class metrics
28 avgArvR{r} = self.getArvR(node, self.model.classes{r});
29 if isempty(avgArvR{r})
30 error(
'Arrival rate data for node %d in class %d is missing.', self.model.getNodeIndex(node), r);
32 avgArvR{r} = avgArvR{r}.data;
34 avgRespT{r} = self.getRespT(node, self.model.classes{r});
35 if isempty(avgRespT{r})
36 error(
'Response time data for node %d in class %d is missing.', self.model.getNodeIndex(node), r);
38 avgRespT{r} = avgRespT{r}.data;
43 avgA = cell2mat(avgArvR);
44 avgR = cell2mat(avgRespT);
47 case 'MATLAB:catenate:dimensionMismatch'
48 error(
'Sampled metrics have different number of samples, use interpolate() before starting this estimation algorithm.');
52% Get solver
class from options (default: @SolverAuto)
53if isfield(self.options, 'solver') && ~isempty(self.options.solver)
54 solverType = self.options.solver;
56 solverType = @SolverAuto;
59[estVal, fObjFun] = mle_data(avgU, avgR, avgA, node.getNumberOfServers(), self.options.iter_max, self.model, node, solverType);
63% MLE procedure using LINE solver for predicted measurements
64function [demEst,fObjFun] = mle_data(cpuUtil, rAvgTimes, avgArvR, numServers, ITERMAX, model, node, solverType)
67 disp('NaN values found for CPU Utilization. Removing NaN values.');
68 cpuUtil = cpuUtil(a == 0);
69 rAvgTimes = rAvgTimes(a == 0,:);
70 avgArvR = avgArvR(a == 0,:);
73a = sum(avgArvR,2) == 0;
75 disp('Removing sampling intervals with zero throughput for all request types.');
76 cpuUtil = cpuUtil(a == 0);
77 rAvgTimes = rAvgTimes(a == 0,:);
78 avgArvR = avgArvR(a == 0,:);
84%% Initialize solver: get sn struct and solver options once
85sn = model.getStruct();
86stIdx = node.stationIndex;
88solver = solverType(model);
89solverOpts = solver.getOptions();
90solverAnalyzer = getSolverAnalyzer(solver, solverOpts);
93x0 = rand(1,R).*max(rAvgTimes);
97options.Display = 'off';
98options.LargeScale = 'off';
99options.MaxIter = ITERMAX;
100options.MaxFunEvals = 1e10;
101options.MaxSQPIter = 5000;
102options.TolCon = 1e-8;
103options.Algorithm = 'interior-point';
105XLB = x0*0 + options.TolCon;
108%% optimization program
110w = avgArvR./(sum(avgArvR,2)*ones(1,R));
111[demEst, fObjFun]=fmincon(@objfun,x0,[],[],[],[],XLB,XUB,[],options);
113 function f = objfun(x)
114 % Update service rates via sn_set_service
116 sn = sn_set_service_coc(sn, stIdx, c, 1/x(c));
119 % Solve model to get predicted response times and utilization
120 [~, U_pred, R_pred] = solverAnalyzer(sn);
122 % Predicted response times and utilization at the target station
123 predR = R_pred(stIdx, 1:R);
124 predU = sum(U_pred(stIdx, 1:R));
126 % Compute objective: weighted response time error + utilization error
127 deltaj = repmat(predR, N, 1) - rAvgTimes;
128 epsi = predU * ones(N, 1) - cpuUtil;
131 f = f + w(i,:) .* deltaj(i,:).^2;
141function solverAnalyzer = getSolverAnalyzer(solver, solverOpts)
142% Return a function handle that takes sn and returns [Q,U,R,T].
143% Selects the appropriate low-level analyzer based on solver type,
144% calling the solver's analyzer function directly to avoid OOP overhead.
145 if isa(solver, 'SolverMVA')
146 solverAnalyzer = @(sn_arg) solver_mva_analyzer(sn_arg, solverOpts);
147 elseif isa(solver, 'SolverAUTO') || isa(solver, 'SolverAuto')
148 mvaOpts = SolverMVA.defaultOptions;
149 solverAnalyzer = @(sn_arg) solver_mva_analyzer(sn_arg, mvaOpts);
150 elseif isa(solver, 'SolverNC')
151 solverAnalyzer = @(sn_arg) solver_nc_analyzer(sn_arg, solverOpts);
152 elseif isa(solver, 'SolverCTMC')
153 solverAnalyzer = @(sn_arg) solver_ctmc_analyzer(sn_arg, solverOpts);
154 elseif isa(solver, 'SolverFluid')
155 solverAnalyzer = @(sn_arg) solver_fluid_analyzer(sn_arg, solverOpts);
156 elseif isa(solver, 'SolverMAM')
157 solverAnalyzer = @(sn_arg) solver_mam_analyzer(sn_arg, solverOpts);
159 % Fallback: wrap via the solver object
160 solverAnalyzer = @(sn_arg) solverAnalyzerWrapper(solver, sn_arg);
164function [Q,U,R,T] = solverAnalyzerWrapper(solver, sn)
165% Generic wrapper: update the model's cached sn and re-solve.
166 solver.model.sn = sn;
168 Q = solver.getAvgQLen();
169 U = solver.getAvgUtil();
170 R = solver.getAvgRespT();
171 T = solver.getAvgTput();