LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
infer_lqn.m
1function [model, info] = infer_lqn(model, paramSpec, obsSpec, Z, options)
2% INFER_LQN Identify hidden LQN parameters from measured performance data.
3%
4% [MODEL, INFO] = INFER_LQN(MODEL, PARAMSPEC, OBSSPEC, Z, OPTIONS) estimates
5% the LQN parameters named in PARAMSPEC (activity host demands and/or task
6% think times) of the LayeredNetwork MODEL from the sequence of performance
7% measurements Z, using an Extended Kalman Filter over the observation model
8% defined by OBSSPEC. It implements Zheng, Yang, Woodside, Litoiu, Iszlai,
9% "Tracking Time-Varying Parameters in Software Systems with Extended Kalman
10% Filters", CASCON 2005. A single measurement column with OPTIONS.QFac = 0
11% reduces to one-shot least-squares calibration.
12%
13% Inputs:
14% PARAMSPEC : struct array of parameters to estimate; see INFER_LQN_SETPARAMS.
15% OBSSPEC : struct array of observed metrics; see INFER_LQN_GETOBS.
16% Z : (no x nsteps) measurements, no == numel(OBSSPEC).
17% OPTIONS : struct with optional fields (defaults in parentheses):
18% solver (@SolverLN) observation-model solver constructor
19% solveropts ([]) options struct passed to the solver
20% QFac (0.1) drift-noise factor, Q_ii=(QFac*a0_i*cvA)^2
21% RFac (0.2) meas.-noise factor, R_ii=((RFac*zbar_i)/1.96)^2/gammaT
22% cvA (1) parameter drift coefficient of variation
23% gammaT ([]) T/Tstar ratio for R (else from T and Tstar, else 1)
24% T, Tstar ([]) measurement interval and system constant
25% Q, R ([]) explicit covariances (override the above)
26% P0 ([]) initial covariance (default diag((0.5*a0)^2))
27% a0 ([]) initial estimate (default: current model values)
28% aTrue ([]) ground truth, enables the Ea RMS metric
29% fdStep (1e-3), fdFloor (1e-6), clampPositive (true), verbose (false)
30%
31% Outputs:
32% MODEL : the LayeredNetwork with the final parameter estimate applied.
33% INFO : EKF result struct (see INFER_LQN_EKF) augmented with ahat, a0,
34% Q, R, P0.
35%
36% Copyright (c) 2012-2026, Imperial College London
37% All rights reserved.
38
39if nargin < 5, options = struct(); end
40
41no = numel(obsSpec);
42if size(Z, 1) ~= no
43 line_error(mfilename, 'Row count of Z must equal numel(obsSpec).');
44end
45
46% observation-model solver and its (silent) options
47solverCtor = infer_lqn_optget(options, 'solver', @SolverLN);
48solveropts = infer_lqn_optget(options, 'solveropts', []);
49if isempty(solveropts)
50 try
51 solveropts = feval([func2str(solverCtor) '.defaultOptions']);
52 solveropts.verbose = VerboseLevel.SILENT;
53 catch
54 solveropts = [];
55 end
56end
57
58% initial parameter estimate (default: current model values)
59a0 = infer_lqn_optget(options, 'a0', []);
60if isempty(a0)
61 a0 = infer_lqn_getparams(model, paramSpec);
62end
63a0 = a0(:);
64
65% drift covariance Q (eq 9a) and measurement covariance R (eq 9b)
66QFac = infer_lqn_optget(options, 'QFac', 0.1);
67RFac = infer_lqn_optget(options, 'RFac', 0.2);
68cvA = infer_lqn_optget(options, 'cvA', 1);
69gammaT = infer_lqn_optget(options, 'gammaT', []);
70if isempty(gammaT)
71 T = infer_lqn_optget(options, 'T', []);
72 Tstar = infer_lqn_optget(options, 'Tstar', []);
73 if ~isempty(T) && ~isempty(Tstar)
74 gammaT = T / Tstar;
75 else
76 gammaT = 1;
77 end
78end
79
80Q = infer_lqn_optget(options, 'Q', []);
81if isempty(Q)
82 qd = (QFac * abs(a0) * cvA).^2; % eq 9a, mean(a_i) ~ a0_i
83 Q = diag(max(qd, eps));
84end
85R = infer_lqn_optget(options, 'R', []);
86if isempty(R)
87 zbar = mean(Z, 2); % mean of z_i across steps
88 rd = ((RFac * abs(zbar)) / 1.96).^2 / gammaT; % eq 9b
89 R = diag(max(rd, eps));
90end
91P0 = infer_lqn_optget(options, 'P0', []);
92if isempty(P0)
93 P0 = diag(max((0.5 * abs(a0)).^2, eps));
94end
95
96% observation model h(a): inject params, solve the LQN, read the metrics
97hfun = @(a) evalObs(model, paramSpec, obsSpec, a, solverCtor, solveropts);
98
99ekfopts = struct( ...
100 'fdStep', infer_lqn_optget(options, 'fdStep', 1e-3), ...
101 'fdFloor', infer_lqn_optget(options, 'fdFloor', 1e-6), ...
102 'clampPositive', infer_lqn_optget(options, 'clampPositive', true), ...
103 'aTrue', infer_lqn_optget(options, 'aTrue', []), ...
104 'verbose', infer_lqn_optget(options, 'verbose', false));
105
106[ahat, info] = infer_lqn_ekf(hfun, a0, P0, Z, Q, R, ekfopts);
107info.ahat = ahat;
108info.a0 = a0;
109info.Q = Q;
110info.R = R;
111info.P0 = P0;
112
113% apply the final estimate to the returned model
114model = infer_lqn_setparams(model, paramSpec, ahat(:, end));
115end
116
117function z = evalObs(model, paramSpec, obsSpec, a, solverCtor, solveropts)
118% Inject parameter vector a, solve the LQN, extract the observation vector.
119model = infer_lqn_setparams(model, paramSpec, a);
120if isempty(solveropts)
121 solver = solverCtor(model);
122else
123 solver = solverCtor(model, solveropts);
124end
125[QN, UN, RN, TN] = solver.getEnsembleAvg();
126lsn = model.getStruct();
127metrics = struct('QLen', QN, 'Util', UN, 'RespT', RN, 'Tput', TN);
128z = infer_lqn_getobs(lsn.names, metrics, obsSpec);
129end
130
131function a0 = infer_lqn_getparams(model, paramSpec)
132% Read the current values of the parameters named in paramSpec.
133np = numel(paramSpec);
134a0 = zeros(np, 1);
135for i = 1:np
136 switch lower(paramSpec(i).type)
137 case 'hostdem'
138 act = infer_lqn_findbyname(model.activities, paramSpec(i).name);
139 if isempty(act)
140 line_error(mfilename, sprintf('Activity ''%s'' not found.', paramSpec(i).name));
141 end
142 a0(i) = act.hostDemandMean;
143 case 'think'
144 tsk = infer_lqn_findbyname(model.tasks, paramSpec(i).name);
145 if isempty(tsk)
146 line_error(mfilename, sprintf('Task ''%s'' not found.', paramSpec(i).name));
147 end
148 a0(i) = tsk.thinkTimeMean;
149 otherwise
150 line_error(mfilename, sprintf('Unknown parameter type ''%s''.', paramSpec(i).type));
151 end
152end
153end
Definition Station.m:245