LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
infer_lqn.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_INFER_INFER_LQN_H
6#define LINE_API_INFER_INFER_LQN_H
7
8/**
9 * @file
10 * @ingroup api_infer
11 * Identify hidden LQN parameters from measured performance data.
12 *
13 * Templated port of matlab/src/api/infer/infer_lqn.m. No JAR counterpart.
14 * Estimates the LQN parameters named in `spec` (activity host demands and
15 * task think times) from the sequence of measurements Z, using the Extended
16 * Kalman Filter of infer_lqn_ekf.h over the observation model defined by
17 * `obs`. It implements Zheng, Yang, Woodside, Litoiu, Iszlai, "Tracking
18 * Time-Varying Parameters in Software Systems with Extended Kalman Filters",
19 * CASCON 2005. A single measurement column with QFac = 0 reduces to one-shot
20 * least-squares calibration.
21 *
22 * The DEFAULT COVARIANCES are the paper's equations (9a) and (9b):
23 * Q_ii = (QFac a0_i cvA)^2, taking mean(a_i) ~ a0_i
24 * R_ii = ((RFac zbar_i)/1.96)^2 / gammaT, with zbar_i the mean of the i-th
25 * measured row over the steps
26 * and P0 = diag((0.5 a0)^2). Each is overridable, and each is floored at the
27 * machine epsilon exactly as MATLAB floors it: a parameter whose initial
28 * estimate is zero would otherwise give a singular Q and stall the filter on
29 * that coordinate forever.
30 *
31 * gammaT is T/Tstar when both are supplied and 1 otherwise, which is what
32 * MATLAB's cascade of infer_lqn_optget calls resolves to.
33 *
34 * THE OBSERVATION MODEL is a required argument here rather than the defaulted
35 * `@SolverLN` of MATLAB. It is MATLAB's options.solver, and in C++ the caller
36 * passes the solve step directly: injecting a default would make api/infer
37 * depend on the whole layered solver, and the algorithm is identical either
38 * way. The callable receives the struct with the candidate parameters already
39 * injected and returns the per-element metric vectors that infer_lqn_getobs
40 * selects from.
41 *
42 * The final estimate is applied to the struct before returning, so the caller
43 * gets back a model carrying the identified parameters, as in MATLAB.
44 *
45 * ARITHMETIC: the filter needs transcendental arithmetic (see
46 * infer_lqn_ekf.h), so the exact instantiation is refused.
47 */
48
49#include <cstddef>
50#include <functional>
51#include <vector>
52
57#include "line/num/number.h"
58#include "line/util/error.h"
59#include "line/util/matrix.h"
60
61namespace line {
62namespace infer {
63
64/**
65 * MATLAB's OPTIONS struct for infer_lqn, with the same defaults the
66 * infer_lqn_optget calls supply. An empty override matrix means "use the
67 * default construction"; a non-empty one is used verbatim.
68 */
69template <class T>
71 double QFac = 0.1; ///< drift-noise factor
72 double RFac = 0.2; ///< measurement-noise factor
73 double cvA = 1.0; ///< parameter drift coefficient of variation
74 bool has_gammaT = false; ///< true when gammaT is given outright
75 double gammaT = 1.0; ///< T/Tstar ratio used in R
76 bool has_T = false; ///< true when the measurement interval is given
77 double T_interval = 0.0; ///< measurement interval
78 bool has_Tstar = false; ///< true when the system constant is given
79 double Tstar = 0.0; ///< system constant
80 Matrix<T> Q; ///< explicit drift covariance, empty for the default
81 Matrix<T> R; ///< explicit measurement covariance, empty for the default
82 Matrix<T> P0; ///< explicit initial covariance, empty for the default
83 std::vector<T> a0; ///< initial estimate, empty to read it off the model
84 std::vector<T> a_true; ///< ground truth, enables the Ea metric
85 double fdStep = 1e-3; ///< relative finite-difference step
86 double fdFloor = 1e-6; ///< minimum absolute perturbation scale
87 bool clampPositive = true; ///< clamp each estimate to at least fdFloor
88};
89
90/** MATLAB's INFO struct: the EKF result plus what the driver constructed. */
91template <class T>
93 EkfResult<T> ekf; ///< the filter output, ahat included
94 std::vector<T> a0; ///< initial estimate actually used
95 Matrix<T> Q; ///< drift covariance actually used
96 Matrix<T> R; ///< measurement covariance actually used
97 Matrix<T> P0; ///< initial covariance actually used
98};
99
100/**
101 * @brief Identify hidden LQN parameters from measured performance data.
102 *
103 * @param lsn layered struct, mutated in place to carry the final estimate
104 * @param spec parameters to identify
105 * @param obs observations to read at each step, numel(obs) == Z.rows()
106 * @param Z (no x nsteps) measurements, one column per step
107 * @param solve observation model: given the struct with candidate parameters
108 * injected, return the per-element metric vectors
109 * @param opt filter and covariance options
110 */
111template <class T>
113 lqn::LqnStruct<T>& lsn, const std::vector<LqnParamSpec>& spec,
114 const std::vector<LqnObsSpec>& obs, const Matrix<T>& Z,
115 const std::function<LqnMetrics<T>(const lqn::LqnStruct<T>&)>& solve,
118 "infer_lqn requires transcendental arithmetic: its filter corrects on a "
119 "finite-difference sensitivity matrix and reports root mean square errors");
120 const T zero = num_traits<T>::from_int(0);
121 const std::size_t no = obs.size();
122 if (Z.rows() != no) throw InputError("infer_lqn: row count of Z must equal numel(obsSpec)");
123 if (spec.empty()) throw InputError("infer_lqn: no parameters to identify");
124 if (!solve) throw InputError("infer_lqn: an observation model is required");
125
126 // initial estimate: the option, or the values currently on the model
127 std::vector<T> a0 = opt.a0;
128 if (a0.empty()) a0 = infer_lqn_getparams(lsn, spec);
129 if (a0.size() != spec.size())
130 throw InputError("infer_lqn: a0 has the wrong length");
131 const std::size_t np = a0.size();
132
133 // MATLAB's eps floor, applied to every constructed diagonal
134 const T eps = num_traits<T>::from_double(2.220446049250313e-16);
135
136 double gammaT = 1.0;
137 if (opt.has_gammaT)
138 gammaT = opt.gammaT;
139 else if (opt.has_T && opt.has_Tstar)
140 gammaT = opt.T_interval / opt.Tstar;
141
143 out.a0 = a0;
144
145 if (opt.Q.rows() != 0) {
146 out.Q = opt.Q;
147 } else {
148 out.Q = Matrix<T>(np, np, zero);
149 const T qf = num_traits<T>::from_double(opt.QFac * opt.cvA);
150 for (std::size_t i = 0; i < np; ++i) {
151 const T qi = qf * num_abs(a0[i]);
152 const T v = qi * qi;
153 out.Q(i, i) = v > eps ? v : eps;
154 }
155 }
156
157 if (opt.R.rows() != 0) {
158 out.R = opt.R;
159 } else {
160 out.R = Matrix<T>(no, no, zero);
161 const T nsteps = num_traits<T>::from_int(static_cast<long>(Z.cols()));
162 const T rf = num_traits<T>::from_double(opt.RFac / 1.96);
163 const T gt = num_traits<T>::from_double(gammaT);
164 for (std::size_t i = 0; i < no; ++i) {
165 T zbar = zero;
166 for (std::size_t k = 0; k < Z.cols(); ++k) zbar += Z(i, k);
167 zbar = zbar / nsteps;
168 const T ri = rf * num_abs(zbar);
169 const T v = ri * ri / gt;
170 out.R(i, i) = v > eps ? v : eps;
171 }
172 }
173
174 if (opt.P0.rows() != 0) {
175 out.P0 = opt.P0;
176 } else {
177 out.P0 = Matrix<T>(np, np, zero);
178 const T half = num_traits<T>::from_double(0.5);
179 for (std::size_t i = 0; i < np; ++i) {
180 const T pi_ = half * num_abs(a0[i]);
181 const T v = pi_ * pi_;
182 out.P0(i, i) = v > eps ? v : eps;
183 }
184 }
185
186 // observation model h(a): inject the parameters, solve, read the metrics
187 lqn::LqnStruct<T>* model = &lsn;
188 const std::vector<LqnParamSpec>* sp = &spec;
189 const std::vector<LqnObsSpec>* ob = &obs;
190 std::function<std::vector<T>(const std::vector<T>&)> hfun =
191 [model, sp, ob, &solve](const std::vector<T>& a) {
192 infer_lqn_setparams(*model, *sp, a);
193 const LqnMetrics<T> m = solve(*model);
194 return infer_lqn_getobs(model->names, m, *ob);
195 };
196
197 EkfOptions<T> ek;
198 ek.fd_step = opt.fdStep;
199 ek.fd_floor = opt.fdFloor;
200 ek.clamp_positive = opt.clampPositive;
201 ek.a_true = opt.a_true;
202 out.ekf = infer_lqn_ekf<T>(hfun, a0, out.P0, Z, out.Q, out.R, ek);
203
204 // apply the final estimate to the returned model
205 std::vector<T> afinal(np, zero);
206 const std::size_t last = out.ekf.ahat.cols() - 1;
207 for (std::size_t i = 0; i < np; ++i) afinal[i] = out.ekf.ahat(i, last);
208 infer_lqn_setparams(lsn, spec, afinal);
209 return out;
210}
211
212} // namespace infer
213} // namespace line
214
215#endif // LINE_API_INFER_INFER_LQN_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Extended Kalman Filter for LQN parameter identification.
Observation vector of a solved LQN, z = h(a).
Apply a parameter vector to a LayeredNetworkStruct, and read it back.
LayeredNetworkStruct, the flattened description of a layered queueing network.
Dense matrix and non-owning view.
InferLqnResult< T > infer_lqn(lqn::LqnStruct< T > &lsn, const std::vector< LqnParamSpec > &spec, const std::vector< LqnObsSpec > &obs, const Matrix< T > &Z, const std::function< LqnMetrics< T >(const lqn::LqnStruct< T > &)> &solve, const InferLqnOptions< T > &opt=InferLqnOptions< T >())
Identify hidden LQN parameters from measured performance data.
Definition infer_lqn.h:112
void infer_lqn_setparams(lqn::LqnStruct< T > &lsn, const std::vector< LqnParamSpec > &spec, const std::vector< T > &a)
Set the parameters named in spec to the values in a, in place.
std::vector< T > infer_lqn_getparams(const lqn::LqnStruct< T > &lsn, const std::vector< LqnParamSpec > &spec)
Read the current values of the parameters named in spec.
std::vector< T > infer_lqn_getobs(const std::vector< std::string > &names, const LqnMetrics< T > &metrics, const std::vector< LqnObsSpec > &spec)
Observation vector of a solved LQN, z = h(a).
EkfResult< T > infer_lqn_ekf(const std::function< std::vector< T >(const std::vector< T > &)> &hfun, const std::vector< T > &a0, const Matrix< T > &P0, const Matrix< T > &Z, const Matrix< T > &Q, const Matrix< T > &R, const EkfOptions< T > &opts=EkfOptions< T >())
Extended Kalman Filter for LQN parameter identification.
T num_abs(const T &v)
Definition number.h:172
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Number-type abstraction for the templated API port.
MATLAB's OPTIONS struct, with the same defaults infer_lqn_optget supplies.
double fd_floor
minimum absolute perturbation scale
bool clamp_positive
clamp each estimate to at least fd_floor
std::vector< T > a_true
ground truth for Ea, empty for none
double fd_step
relative finite-difference step
MATLAB's [ahat, info] return list.
MATLAB's OPTIONS struct for infer_lqn, with the same defaults the infer_lqn_optget calls supply.
Definition infer_lqn.h:70
double gammaT
T/Tstar ratio used in R.
Definition infer_lqn.h:75
std::vector< T > a0
initial estimate, empty to read it off the model
Definition infer_lqn.h:83
double fdStep
relative finite-difference step
Definition infer_lqn.h:85
bool clampPositive
clamp each estimate to at least fdFloor
Definition infer_lqn.h:87
double fdFloor
minimum absolute perturbation scale
Definition infer_lqn.h:86
double QFac
drift-noise factor
Definition infer_lqn.h:71
std::vector< T > a_true
ground truth, enables the Ea metric
Definition infer_lqn.h:84
Matrix< T > R
explicit measurement covariance, empty for the default
Definition infer_lqn.h:81
bool has_gammaT
true when gammaT is given outright
Definition infer_lqn.h:74
double RFac
measurement-noise factor
Definition infer_lqn.h:72
bool has_Tstar
true when the system constant is given
Definition infer_lqn.h:78
Matrix< T > P0
explicit initial covariance, empty for the default
Definition infer_lqn.h:82
bool has_T
true when the measurement interval is given
Definition infer_lqn.h:76
double Tstar
system constant
Definition infer_lqn.h:79
double cvA
parameter drift coefficient of variation
Definition infer_lqn.h:73
double T_interval
measurement interval
Definition infer_lqn.h:77
Matrix< T > Q
explicit drift covariance, empty for the default
Definition infer_lqn.h:80
MATLAB's INFO struct: the EKF result plus what the driver constructed.
Definition infer_lqn.h:92
Matrix< T > Q
drift covariance actually used
Definition infer_lqn.h:95
std::vector< T > a0
initial estimate actually used
Definition infer_lqn.h:94
Matrix< T > R
measurement covariance actually used
Definition infer_lqn.h:96
EkfResult< T > ekf
the filter output, ahat included
Definition infer_lqn.h:93
Matrix< T > P0
initial covariance actually used
Definition infer_lqn.h:97
Per-element metric vectors, each aligned with the element name list.
std::vector< std::string > names
(nidx+1) declared name
Definition lqn_struct.h:212