1classdef ParamEstimator < handle
2 % Abstract
class for service demand estimators
4 % Copyright (c) 2012-2026, Imperial College London
7 properties (Access =
public)
8 options; % Data structure with engine options
9 model; % Model to be analyzed
10 samples; % Input dataset
11 samplesAggr; % Input dataset
16 function self = ParamEstimator(model, options)
17 % SELF = SOLVER(MODEL, NAME, OPTIONS)
18 if ~exist('options','var')
19 options = self.defaultOptions;
22 self.options = options;
23 self.samples = cell(model.getNumberOfNodes, model.getNumberOfClasses);
24 self.samplesAggr = cell(model.getNumberOfNodes, 1);
29 function self = addSamples(self, sampleData)
30 i = self.model.getNodeIndex(sampleData.node);
32 if sampleData.isAggregate
34 self.samplesAggr{i}{end+1} = sampleData;
36 r = self.model.getClassIndex(sampleData.class);
38 self.samples{i,r}{end+1} = sampleData;
42 function data = getData(self)
46 function data = getDataAggr(self)
47 data = self.samplesAggr;
50 % look into the data available
for that node and
class for the
52 function data = getArvR(self, node,
jobclass)
54 i = self.model.getNodeIndex(node);
55 r = self.model.getClassIndex(
jobclass);
56 nodeData = self.samples{i,r};
57 for d=1:length(nodeData)
58 if nodeData{d}.type == MetricType.ArvR
65 % look into the data available
for that node and
class for the
67 function data = getUtil(self, node,
jobclass)
69 i = self.model.getNodeIndex(node);
70 r = self.model.getClassIndex(
jobclass);
71 nodeData = self.samples{i,r};
72 for d=1:length(nodeData)
73 if nodeData{d}.type == MetricType.Util
80 % look into the data available
for that node and
class for the
82 function data = getRespT(self, node,
jobclass)
84 i = self.model.getNodeIndex(node);
85 r = self.model.getClassIndex(
jobclass);
86 nodeData = self.samples{i,r};
87 for d=1:length(nodeData)
88 if nodeData{d}.type == MetricType.RespT
95 % look into the data available
for that node and
class for the
97 function data = getAggrUtil(self, node)
99 i = self.model.getNodeIndex(node);
100 nodeAggrData = self.samplesAggr{i};
101 for d=1:length(nodeAggrData)
102 if nodeAggrData{d}.type == MetricType.Util
103 data = nodeAggrData{d};
109 % look into the data available
for that node and
class for the
111 function data = getQLen(self, node,
jobclass, ev)
113 i = self.model.getNodeIndex(node);
114 r = self.model.getClassIndex(
jobclass);
115 nodeData = self.samples{i,r};
116 if ~exist(
'ev',
'var')
117 for d=1:length(nodeData)
118 if nodeData{d}.type == MetricType.QLen
119 data{end+1} = nodeData{d};
126 for d=1:length(nodeData)
127 % e.g., arrival queue-length
128 if nodeData{d}.type == MetricType.QLen && (nodeData{d}.cond.node == ev.node) && (nodeData{d}.cond.class == ev.class) && (nodeData{d}.cond.event == ev.event)
136 % look into the data available
for that node and
class for the
138 function data = getAggrQLen(self, node, ev)
140 i = self.model.getNodeIndex(node);
141 nodeData = self.samplesAggr{i};
142 if ~exist(
'ev',
'var')
143 for d=1:length(nodeData)
144 if nodeData{d}.type == MetricType.QLen
150 for d=1:length(nodeData)
151 % e.g., arrival queue-length
152 if nodeData{d}.type == MetricType.QLen && (nodeData{d}.cond.node == ev.node) && (nodeData{d}.cond.class == ev.class) && (nodeData{d}.cond.event == ev.event)
160 % look into the data available
for that node and
class for the
162 function data = getTput(self, node,
jobclass)
164 i = self.model.getNodeIndex(node);
165 r = self.model.getClassIndex(
jobclass);
166 nodeData = self.samples{i,r};
167 for d=1:length(nodeData)
168 if nodeData{d}.type == MetricType.Tput
175 function method = autoMethod(self)
176 % AUTOMETHOD Automatically select the best estimation method
177 % based on the available sampled metrics.
178 sn = self.model.getStruct;
179 hasArvR =
false; hasRespT =
false; hasUtil =
false;
180 hasQLen =
false; hasTput =
false; hasTrace =
false;
181 hasAggrUtil =
false; hasAggrQLen =
false;
183 % scan per-
class metrics
184 for i = 1:size(self.samples, 1)
185 for r = 1:size(self.samples, 2)
186 nodeData = self.samples{i,r};
187 for d = 1:length(nodeData)
189 if sm.type == MetricType.ArvR, hasArvR =
true; end
190 if sm.type == MetricType.RespT, hasRespT =
true; end
191 if sm.type == MetricType.Util, hasUtil =
true; end
192 if sm.type == MetricType.QLen, hasQLen =
true; end
193 if sm.type == MetricType.Tput, hasTput =
true; end
194 if sm.isTrace(), hasTrace =
true; end
199 % scan aggregate metrics
200 for i = 1:size(self.samplesAggr, 1)
201 nodeAggrData = self.samplesAggr{i};
202 for d = 1:length(nodeAggrData)
203 sm = nodeAggrData{d};
204 if sm.type == MetricType.Util, hasAggrUtil =
true; end
205 if sm.type == MetricType.QLen, hasAggrQLen =
true; end
209 % select method based on available data
210 if hasTrace && hasRespT && hasAggrQLen
212 elseif hasTrace && hasRespT && hasArvR
214 elseif hasArvR && hasRespT && (hasUtil || hasAggrUtil)
216 elseif hasArvR && (hasUtil || hasAggrUtil)
221 error(
'Insufficient data to automatically select an estimation method. Please set options.method manually.');
224 self.options.method = method;
228 estVal = estimateAt(self,
nodes);
229 estVal = estimator_ubo(self,
nodes);
230 estVal = estimator_ubr(self,
nodes);
231 estVal = estimator_erps(self,
nodes);
232 estVal = estimator_ekf(self,
nodes);
233 estVal = estimator_mcmc(self,
nodes);
234 estVal = estimator_mle(self,
nodes);
235 estVal = estimator_rnn(self,
nodes);
236 estVal = estimator_mlps(self,
nodes);
237 estVal = estimator_fmlps(self,
nodes);
238 estVal = estimator_qmle(self,
nodes);
239 [eqModel, eqNode] = buildClosedEquivalentForPS(self, node);
240 estVal = estimator_gibbs(self,
nodes);
244 function options = defaultOptions()
245 % OPTIONS = DEFAULTOPTIONS()
246 % Return
default options
249 options.method =
'ubr';
250 options.variant =
'default';
251 options.iter_max = 1000;
253 options.solver = @SolverAuto;
254 options.openPopulation = 100;
257 function desc = getRequiredMetrics(method)
258 % GETREQUIREDMETRICS Return description of metrics required
259 % by each estimation method.
262 desc =
'ArvR (per-class) + Util (per-class or aggregate)';
264 desc =
'ArvR (per-class) + RespT (per-class) + Util (aggregate)';
266 desc =
'RespT (per-class) + QLen (aggregate, conditional on class arrivals). PS stations only.';
268 desc =
'RespT (per-class) + Util (aggregate). Sequential/recursive estimation.';
270 desc =
'QLen (aggregate). Gibbs sampling with MCMC. Open/mixed via closed equivalence.';
272 desc =
'ArvR (per-class) + RespT (per-class) + Util (aggregate)';
274 desc =
'QLen (per-class, trace format). Transient queue-length traces.';
276 desc =
'ArvR (per-class, trace) + RespT (per-class, trace). PS stations only. Open/mixed via closed equivalence.';
278 desc =
'ArvR (per-class, trace) + RespT (per-class, trace). PS stations only. Open/mixed via closed equivalence.';
280 desc =
'QLen (per-class). Open/mixed via closed equivalence (Z_r = N_r / lambda_r).';
282 desc =
'ArvR (per-class, trace) + RespT (per-class, trace) + Tput (per-class). Gibbs sampling.';
284 desc = sprintf(
'Unknown method: %s', method);