1function demandEst = infer_fmlps(model, node, rt,
class, ql, W)
2% INFER_FMLPS FMLPS demand estimation
using sn
struct-level operations.
4% Estimates service demands at a PS queue
using the Fluid Maximum
5% Likelihood
for Processor Sharing method. Uses sn_set_service
for
6% fast parameter updates, avoiding model.reset()/getStruct() in the
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)
15% W - total population (number of threads/jobs)
18% demandEst - 1 x R vector of estimated mean service demands
20% Copyright (c) 2012-2026, Imperial College London
22% This code
is released under the 3-Clause BSD License.
24sn = model.getStruct();
26V = node.getNumberOfServers();
27stIdx = node.stationIndex;
29xLB = min(rt) * ones(1, R) / W;
30xUB = max(rt) * ones(1, R);
32% Initial point estimate
33meanQL = mean(sum(ql, 2));
34Vtilde = min(meanQL, V);
37 if ~isempty(rt(class == j))
38 x0(j) = Vtilde * mean(rt(class == j)) / meanQL;
44% Cache station indices (invariant across iterations)
47for ii = 1:sn.nstations
48 if sn.sched(ii) == SchedStrategy.INF
55% Cache delay rates (invariant across iterations)
56delayRates = zeros(1, R);
58 delayRates(kk) = sn.mu{delayIdx}{kk}(1);
61%% Optimization options
63options.Display =
'iter';
64options.Algorithm =
'interior-point';
65options.LargeScale =
'on';
66options.MaxIter = 1e10;
67options.MaxFunEvals = 1e10;
68options.MaxSQPIter = 5000;
72[demandEst, ~] = fmincon(@objfun, x0, [], [], [], [], xLB, xUB, [], options);
74 function f = objfun(x)
77 % Update service rates (cell-of-cells format
for solver compatibility)
79 sn = sn_set_service_coc(sn, stIdx, r, 1/x(r));
82 % Build augmented ODE
for each unique tagged
class (once per iteration)
83 uniqueTC = unique(
class);
84 ftemp = zeros(size(rt));
86 for u = 1:length(uniqueTC)
92 % Build augmented model and ODE handle from sn
struct directly
93 [ode_h, q_idx, augPhases] = infer_fluid_ps_rt_likelihood(sn, tc);
95 % Solve each sample reusing the same ODE handle
96 ftemp_tc = zeros(size(rt_tc));
97 for rr = 1:length(rt_tc)
98 like = solve_fmlps_sample(ode_h, q_idx, augPhases, ...
99 delayRates, delayIdx, refIdx, R, ...
100 ql_tc(rr,:), W, tc, rt_tc(rr));
101 ftemp_tc(rr) = log(TOL + like);
103 ftemp(mask) = ftemp_tc;
111function LIKE = solve_fmlps_sample(ode_h, q_indices, augPhases, ...
112 delayRates, delayIdx, refIdx, K, aQueue, W, taggedClass, Rsampled)
113% Solve the fluid ODE
for a single sample and extract the response time likelihood.
115% Uses the pre-built ODE handle from the augmented model. Only computes
116% the sample-specific initial condition and solves the ODE.
121% Compute initial fluid levels
122y0_levels = zeros(size(augPhases, 1), K);
124% Delay station: distribute remaining fluid proportionally to delay rates
125delayJobs = (W - sum(aQueue)) * delayRates / sum(delayRates);
126y0_levels(delayIdx, :) = delayJobs;
128% Queue station: observed queue lengths per
class
129y0_levels(refIdx, :) = aQueue;
131% Build state vector from fluid levels
132totalPhases = sum(augPhases(:));
133y0 = zeros(1, totalPhases);
134M = size(augPhases, 1);
137 if augPhases(i, k) > 0
138 y0(q_indices(i, k)) = y0_levels(i, k);
143% Move fluid from taggedClass to Tagged at refNode
144y0(q_indices(refIdx, taggedClass)) = y0(q_indices(refIdx, taggedClass)) - newFluid;
145y0(q_indices(refIdx, newK)) = newFluid;
147% Solve ODE from 0 to Rsampled
148refTagIdx = q_indices(refIdx, newK);
149opt = odeset(
'AbsTol', 1e-8,
'RelTol', 1e-5,
'NonNegative', 1:length(y0), ...
150 'Events', @(t,y) ode_events(t, y, refTagIdx));
151[t, yt] = ode15s(ode_h, [0 Rsampled], y0, opt);
153% Extract likelihood from terminal state derivative
155 lastState = yt(end,:);
156 lastRates = ode_h(t(end), lastState
');
157 LIKE = -lastRates(refTagIdx) / newFluid;
165function [value, isterminal, direction] = ode_events(~, y, idx)