LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
infer_gibbs.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_GIBBS_H
6#define LINE_API_INFER_INFER_GIBBS_H
7
8/**
9 * @file
10 * @ingroup api_infer
11 * Gibbs sampling demand estimator for a closed delay-plus-queue model.
12 *
13 * Templated port of matlab/src/api/infer/infer_gibbs.m. No JAR counterpart.
14 *
15 * The observed data are per-class arrival and departure traces at one station.
16 * The estimator replays them into a continuous-time record of the population
17 * vector, turns that record into an empirical distribution over states,
18 * samples a test set from it, and then draws the per-class demands from their
19 * conditional posteriors one coordinate at a time. The posterior of a single
20 * demand is a slice: on a grid of candidate values,
21 *
22 * log p(theta_j) = (sum over the test set of the class-j queue count)
23 * log theta_j - n_test log G(theta),
24 *
25 * i.e. the product-form likelihood with the normalizing constant carried
26 * along. G is never evaluated directly. It is propagated along the grid with
27 * the exact derivative identity of the closed product-form constant,
28 * d log G / d theta_j = Q_j / theta_j, integrated one grid step at a time with
29 * Q from Bard-Schweitzer AMVA (the 'TE' method of the MATLAB). That is why
30 * every grid step costs one pfqn_bs solve, warm started from the previous
31 * point, and why the walk starts at the current theta with the running log G
32 * the caller carries between coordinate updates.
33 *
34 * WHAT IS AND IS NOT PORTED. MATLAB's 'MCI' branch and its pdf_slice helper
35 * are unreachable: alg is assigned 'TE' as a literal with no way in, and
36 * pdf_slice is DECLARED with eleven parameters and CALLED with ten from the
37 * one dead call site, so the MCI branch would raise on its first use. Dead,
38 * broken code is not ported. Everything the 'TE' path executes is.
39 *
40 * The four sample budgets that MATLAB hard-codes (data_needed, the test set
41 * size, the chain length, and the 50-sample convergence block) are options
42 * here, defaulting to MATLAB's values. That is a strict superset: the default
43 * construction reproduces the MATLAB exactly, and a caller that wants a short
44 * chain no longer has to edit the source.
45 *
46 * RANDOMNESS. Two places draw: the test set is sampled from the empirical
47 * state distribution by inversion, and each coordinate update is drawn from
48 * its normalized slice. Both take an explicit McRng, so a run is reproducible
49 * from its seed. MATLAB's Mersenne Twister stream is deliberately NOT
50 * reproduced -- same algorithm, different stream -- so the two implementations
51 * agree in distribution and on every deterministic intermediate, not sample by
52 * sample.
53 *
54 * MATLAB's `eps` in the derivative denominators is the DOUBLE machine epsilon,
55 * a fixed constant of the formula rather than a property of the working
56 * arithmetic, so the port carries the literal 2^-52 into T instead of asking
57 * the arithmetic for its own epsilon. Substituting Real50's epsilon would
58 * change the first grid step, where theta = 0 and the constant is the entire
59 * denominator.
60 *
61 * ARITHMETIC: logarithms throughout, plus a fixed-point AMVA iteration per
62 * grid point, so the routine is gated on transcendental arithmetic and
63 * registered for Double only. Real is NOT registered: log(0) at the first grid
64 * point is a defined -infinity in IEEE double and the slice weight it produces
65 * underflows to exactly zero, which is the behaviour the algorithm relies on;
66 * that is a property of the double arithmetic MATLAB runs in, and the port
67 * does not claim a high-precision instantiation it has not validated.
68 */
69
70#include <algorithm>
71#include <cmath>
72#include <cstddef>
73#include <limits>
74#include <map>
75#include <vector>
76
79#include "line/num/number.h"
80#include "line/util/error.h"
81#include "line/util/matrix.h"
82
83namespace line {
84namespace infer {
85
86/** The two nodes of the model the estimator assumes: delay, then queue. */
87static const std::size_t GIBBS_NNODES = 2;
88
89/**
90 * One class's trace. MATLAB reads these from rows 3, 4 and 6 of its 6 x (K+1)
91 * cell array; the cell container is a MATLAB storage detail, so the port takes
92 * the three sample vectors directly, as infer_get_qlen_arrival already does.
93 */
94template <class T>
95struct GibbsTrace {
96 std::vector<T> arrival_ms; ///< data{3,k}, arrival times in MILLISECONDS
97 std::vector<T> respt_s; ///< data{4,k}, response times in SECONDS
98 std::vector<T> think_obs; ///< data{6,k}, the think-time normalizer
99};
100
101/** The empirical state distribution built from the traces. */
102template <class T>
104 Matrix<long> states; ///< (ns x K*GIBBS_NNODES) population vectors, node-major
105 std::vector<T> prob; ///< (ns) time fraction spent in each state
106 std::vector<long> N; ///< (K) per-class population
107 std::vector<T> N0; ///< (K) per-class mean number at the queue
108};
109
110/** MATLAB's hard-coded budgets, exposed with their MATLAB values as defaults. */
112 double tol = 1e-3; ///< grid step and convergence tolerance
113 std::size_t data_needed = 200000; ///< events kept from the end of the trace
114 std::size_t likelihood_sample = 5000; ///< test set size
115 std::size_t nsamples = 2000; ///< chain length
116 std::size_t block = 50; ///< samples per convergence block
117};
118
119/** The deterministic content of one coordinate update. */
120template <class T>
122 std::vector<T> grid; ///< candidate values, 0, interval, 2 interval, ...
123 std::vector<T> logG; ///< log normalizing constant along the grid
124 std::vector<T> prob; ///< normalized slice probability
125 T range_size_dim; ///< twice the value at which the slice mass closes
126};
127
128namespace detail {
129
130/** MATLAB's eps, the double machine epsilon, as a constant of the formula. */
131template <class T>
132T gibbs_eps() {
133 return num_traits<T>::from_double(2.220446049250313e-16);
134}
135
136/** Number of points of MATLAB's 0:step:limit, which counts floor(limit/step)+1. */
137inline std::size_t colon_count(double limit, double step) {
138 if (!(step > 0.0)) throw InputError("infer_gibbs: the grid step must be positive");
139 if (limit < 0.0) return 0;
140 return static_cast<std::size_t>(std::floor(limit / step + 1e-10)) + 1;
141}
142
143} // namespace detail
144
145/**
146 * Empirical state distribution of the replayed traces (MATLAB's analyseData).
147 *
148 * Each class contributes one event per sample at the arrival time and one at
149 * the arrival plus the response time, so the record is a birth-death walk of
150 * the population vector between the delay node and the queue. The population
151 * of each class is taken as the largest count the walk ever reaches, which is
152 * what makes the delay-node counts non-negative once it is added back.
153 *
154 * The state is integer by construction, so it is carried as long and only the
155 * holding times are in T: the aggregation over repeated states is then exact
156 * in every arithmetic instead of accumulating a rounding per event.
157 *
158 * @param data per-class traces
159 * @param data_needed events kept from the END of the record, 0 for all
160 */
161template <class T>
163 std::size_t data_needed) {
164 const std::size_t K = data.size();
165 if (K == 0) throw InputError("gibbs_analyse_data: no classes");
166
167 // Event stream: an arrival and a departure per sample of every class.
168 struct Ev {
169 T t;
170 std::size_t cls;
171 int logger; // 1 = into the queue, 2 = back to the delay
172 };
173 const T thousand = num_traits<T>::from_int(1000);
174 std::vector<Ev> ev;
175 for (std::size_t k = 0; k < K; ++k) {
176 const std::size_t n = data[k].arrival_ms.size();
177 if (data[k].respt_s.size() != n)
178 throw InputError("gibbs_analyse_data: a class has mismatched sample counts");
179 for (std::size_t i = 0; i < n; ++i) {
180 Ev a;
181 a.t = data[k].arrival_ms[i];
182 a.cls = k;
183 a.logger = 1;
184 ev.push_back(a);
185 }
186 for (std::size_t i = 0; i < n; ++i) {
187 Ev d;
188 d.t = T(data[k].arrival_ms[i] + data[k].respt_s[i] * thousand);
189 d.cls = k;
190 d.logger = 2;
191 ev.push_back(d);
192 }
193 }
194 const std::size_t total = ev.size();
195 if (total == 0) throw InputError("gibbs_analyse_data: empty traces");
196 // MATLAB's sort is stable, and ties between an arrival and a departure at
197 // the same instant change the state sequence, so the order matters.
198 std::stable_sort(ev.begin(), ev.end(),
199 [](const Ev& a, const Ev& b) { return a.t < b.t; });
200
201 // Replay. count(i) is the state that HOLDS from ev[i-1].t to ev[i].t.
202 Matrix<long> count(total, K * GIBBS_NNODES, 0L);
203 for (std::size_t i = 0; i + 1 < total; ++i) {
204 for (std::size_t c = 0; c < K * GIBBS_NNODES; ++c) count(i + 1, c) = count(i, c);
205 const std::size_t k = ev[i].cls;
206 const std::size_t from = static_cast<std::size_t>(ev[i].logger) - 1;
207 const std::size_t to = (from + 1) % GIBBS_NNODES;
208 count(i + 1, from * K + k) -= 1;
209 count(i + 1, to * K + k) += 1;
210 }
211
213 out.N.assign(K, 0L);
214 for (std::size_t k = 0; k < K; ++k) {
215 long m = 0;
216 for (std::size_t i = 0; i < total; ++i)
217 for (std::size_t nd = 0; nd < GIBBS_NNODES; ++nd)
218 if (count(i, nd * K + k) > m) m = count(i, nd * K + k);
219 out.N[k] = m;
220 }
221 for (std::size_t i = 0; i < total; ++i)
222 for (std::size_t k = 0; k < K; ++k) count(i, k) += out.N[k];
223
224 // burnin index rationale: see _kb/03-api-layer.md (cpp port notes: infer)
225 std::size_t burnin = 0; // 0-based
226 if (data_needed != 0) {
227 if (total == data_needed)
228 throw InputError(
229 "gibbs_analyse_data: the record is exactly data_needed events long, which MATLAB "
230 "indexes from zero");
231 if (total > data_needed) burnin = total - data_needed - 1;
232 }
233
234 // state holding-time aggregation order: see _kb/03-api-layer.md (cpp port notes: infer)
235 const T zero = num_traits<T>::from_int(0);
236 std::map<std::vector<long>, T> acc;
237 for (std::size_t i = burnin; i < total; ++i) {
238 std::vector<long> key(K * GIBBS_NNODES);
239 for (std::size_t c = 0; c < K * GIBBS_NNODES; ++c) key[c] = count(i, c);
240 const T dt = i == 0 ? zero : T(ev[i].t - ev[i - 1].t);
241 typename std::map<std::vector<long>, T>::iterator it = acc.find(key);
242 if (it == acc.end())
243 acc.insert(std::make_pair(key, dt));
244 else
245 it->second += dt;
246 }
247
248 const T obs_length = ev[total - 1].t - ev[burnin].t;
249 if (obs_length <= zero) throw NumericError("gibbs_analyse_data: the record has no duration");
250
251 out.states = Matrix<long>(acc.size(), K * GIBBS_NNODES, 0L);
252 out.prob.assign(acc.size(), zero);
253 std::size_t r = 0;
254 for (typename std::map<std::vector<long>, T>::const_iterator it = acc.begin();
255 it != acc.end(); ++it, ++r) {
256 for (std::size_t c = 0; c < K * GIBBS_NNODES; ++c) out.states(r, c) = it->first[c];
257 out.prob[r] = it->second / obs_length;
258 }
259
260 out.N0.assign(K, zero);
261 for (std::size_t k = 0; k < K; ++k) {
262 T s = zero;
263 for (std::size_t i = 0; i < out.prob.size(); ++i)
264 s += out.prob[i] * num_traits<T>::from_int(out.states(i, K + k));
265 out.N0[k] = s;
266 }
267 return out;
268}
269
270/**
271 * The deterministic half of one coordinate update: the log normalizing
272 * constant along the grid and the normalized slice it implies.
273 *
274 * @param think_time (K) think times of the delay node
275 * @param theta (K) current demands; theta[index] must lie ON the grid
276 * @param testset (nt x K*GIBBS_NNODES) sampled states
277 * @param index coordinate being updated
278 * @param N (K) populations
279 * @param logG_init running log G at the current theta
280 * @param interval grid step
281 * @param range_size upper end of the grid
282 */
283template <class T>
284GibbsSlice<T> gibbs_slice(const std::vector<T>& think_time, const std::vector<T>& theta,
285 const Matrix<long>& testset, std::size_t index,
286 const std::vector<T>& N, const T& logG_init, double interval,
287 const T& range_size) {
289 "gibbs_slice requires transcendental arithmetic: it integrates "
290 "d log G / d theta along a grid in logarithms");
291 using std::exp;
292 using std::log;
293
294 const std::size_t K = theta.size();
295 if (think_time.size() != K) throw InputError("gibbs_slice: think_time has the wrong length");
296 if (N.size() != K) throw InputError("gibbs_slice: N has the wrong length");
297 if (index >= K) throw InputError("gibbs_slice: coordinate out of range");
298 if (testset.cols() != K * GIBBS_NNODES)
299 throw InputError("gibbs_slice: testset has the wrong width");
300
301 const T zero = num_traits<T>::from_int(0);
302 const T one = num_traits<T>::from_int(1);
303 const T eps = detail::gibbs_eps<T>();
304 const T step = num_traits<T>::from_double(interval);
305
306 GibbsSlice<T> out;
307 const std::size_t n =
308 detail::colon_count(num_traits<T>::to_double(range_size), interval);
309 if (n == 0) throw NumericError("gibbs_slice: the candidate grid collapsed to nothing");
310 out.grid.assign(n, zero);
311 for (std::size_t i = 0; i < n; ++i)
312 out.grid[i] = num_traits<T>::from_int(static_cast<long>(i)) * step;
313 out.logG.assign(n, zero);
314
315 // grid-miss rationale: see _kb/03-api-layer.md (cpp port notes: infer)
316 std::size_t ip = 0;
317 bool found = false;
318 for (std::size_t i = 0; i < n && !found; ++i)
319 if (out.grid[i] == theta[index]) {
320 ip = i;
321 found = true;
322 }
323
324 if (found) {
325 Matrix<T> L(1, K, zero);
326 for (std::size_t j = 0; j < K; ++j) L(0, j) = theta[j];
327 out.logG[ip] = logG_init;
328
330 pfqn::pfqn_bs(L, N, think_time, std::vector<pfqn::AmvaSched>());
331 Matrix<T> QN = res.QN;
332 for (std::size_t i = ip; i-- > 0;) {
333 L(0, index) = out.grid[i + 1];
334 res = pfqn::pfqn_bs(L, N, think_time, std::vector<pfqn::AmvaSched>(), interval, 1000,
335 QN);
336 QN = res.QN;
337 const T d = one - QN(0, index) / (out.grid[i + 1] + eps) * step;
338 out.logG[i] = d < zero ? out.logG[i + 1] : T(out.logG[i + 1] + log(d));
339 }
340
341 L(0, index) = theta[index];
342 res = pfqn::pfqn_bs(L, N, think_time, std::vector<pfqn::AmvaSched>());
343 QN = res.QN;
344 for (std::size_t i = ip + 1; i < n; ++i) {
345 L(0, index) = out.grid[i - 1];
346 res = pfqn::pfqn_bs(L, N, think_time, std::vector<pfqn::AmvaSched>(), interval, 1000,
347 QN);
348 QN = res.QN;
349 const T d = one + QN(0, index) / (out.grid[i - 1] + eps) * step;
350 out.logG[i] = d < zero ? out.logG[i - 1] : T(out.logG[i - 1] + log(d));
351 }
352 }
353
354 // Slice log density. The coefficient is the total number of class-index
355 // jobs at the queue over the whole test set.
356 T coeff = zero;
357 for (std::size_t i = 0; i < testset.rows(); ++i)
358 coeff += num_traits<T>::from_int(testset(i, K + index));
359 if (coeff == zero)
360 throw NumericError(
361 "gibbs_slice: no sampled state has a job of this class at the queue, so the slice "
362 "density is 0 log 0 at the first grid point and carries no information");
363 const T nt = num_traits<T>::from_int(static_cast<long>(testset.rows()));
364
365 std::vector<T> lp(n, zero);
366 for (std::size_t i = 0; i < n; ++i) lp[i] = coeff * log(out.grid[i]) - out.logG[i] * nt;
367 T mx = lp[0];
368 for (std::size_t i = 1; i < n; ++i)
369 if (lp[i] > mx) mx = lp[i];
370 out.prob.assign(n, zero);
371 T tot = zero;
372 for (std::size_t i = 0; i < n; ++i) {
373 out.prob[i] = exp(T(lp[i] - mx));
374 tot += out.prob[i];
375 }
376 if (!(tot > zero)) throw NumericError("gibbs_slice: the slice has no mass");
377 for (std::size_t i = 0; i < n; ++i) out.prob[i] = out.prob[i] / tot;
378
379 // empty-find slice-mass rationale: see _kb/03-api-layer.md (cpp port notes: infer)
380 T cum = zero;
381 const T closed = one - num_traits<T>::from_double(1e-10);
382 out.range_size_dim = out.grid[n - 1] * num_traits<T>::from_int(2);
383 for (std::size_t i = 0; i < n; ++i) {
384 cum += out.prob[i];
385 if (cum > closed) {
387 break;
388 }
389 }
390 return out;
391}
392
393/**
394 * Estimated per-class mean demands.
395 *
396 * @param data per-class traces
397 * @param nbCores number of processors of the queue node
398 * @param opts sample budgets and tolerance
399 * @param rng generator, advanced by the call
400 * @return (K) estimated demands
401 */
402template <class T>
403std::vector<T> infer_gibbs(const std::vector<GibbsTrace<T>>& data, const T& nbCores,
404 const GibbsOptions& opts, pfqn::McRng& rng) {
406 "infer_gibbs requires transcendental arithmetic: it samples a slice of a "
407 "log density built from an iteratively integrated normalizing constant");
408 using std::log;
409
410 const std::size_t K = data.size();
411 if (K == 0) throw InputError("infer_gibbs: no classes");
412 if (opts.block == 0) throw InputError("infer_gibbs: the convergence block must be positive");
413 if (opts.nsamples == 0) throw InputError("infer_gibbs: the chain must have samples");
414 if (!(opts.tol > 0.0)) throw InputError("infer_gibbs: the tolerance must be positive");
415
416 const T zero = num_traits<T>::from_int(0);
417 const T one = num_traits<T>::from_int(1);
418
420 const std::size_t ns = st.prob.size();
421
422 // usedCores rationale: see _kb/03-api-layer.md (cpp port notes: infer)
423 T used = zero;
424 for (std::size_t i = 0; i < ns; ++i) {
425 T q = zero;
426 for (std::size_t k = 0; k < K; ++k) q += num_traits<T>::from_int(st.states(i, K + k));
427 used += (q > nbCores ? nbCores : q) * st.prob[i];
428 }
429 const T denom = one - st.prob[ns - 1];
430 if (denom == zero) throw NumericError("infer_gibbs: the record holds a single state");
431 used = used / denom;
432
433 std::vector<T> Nt(K, zero), think_time(K, zero);
434 for (std::size_t k = 0; k < K; ++k) {
435 Nt[k] = num_traits<T>::from_int(st.N[k]);
436 if (data[k].think_obs.empty())
437 throw InputError("infer_gibbs: a class has no think-time observations");
438 T s = zero;
439 for (std::size_t i = 0; i < data[k].think_obs.size(); ++i) s += data[k].think_obs[i];
440 const T m = s / num_traits<T>::from_int(static_cast<long>(data[k].think_obs.size()));
441 if (m == zero) throw NumericError("infer_gibbs: a class has a zero think-time mean");
442 think_time[k] = (Nt[k] - st.N0[k]) / m;
443 if (!(think_time[k] > zero))
444 throw NumericError("infer_gibbs: a class has a non-positive think time");
445 }
446
447 // Test set: states drawn from the empirical distribution by inversion.
448 std::vector<T> cum(ns, zero);
449 T c = zero;
450 for (std::size_t i = 0; i < ns; ++i) {
451 c += st.prob[i];
452 cum[i] = c;
453 }
454 Matrix<long> testset(opts.likelihood_sample, K * GIBBS_NNODES, 0L);
455 for (std::size_t s = 0; s < opts.likelihood_sample; ++s) {
456 const T u = pfqn::mc_uniform<T>(rng);
457 std::size_t pick = ns; // MATLAB indexes an empty find and raises
458 for (std::size_t i = 0; i < ns; ++i)
459 if (u < cum[i]) {
460 pick = i;
461 break;
462 }
463 if (pick == ns)
464 throw NumericError("infer_gibbs: the empirical distribution does not sum to one");
465 for (std::size_t j = 0; j < K * GIBBS_NNODES; ++j) testset(s, j) = st.states(pick, j);
466 }
467
468 // log G of the demand-free model: all jobs at the delay node.
469 T logG = zero;
470 for (std::size_t k = 0; k < K; ++k) {
471 logG += Nt[k] * log(think_time[k]);
472 for (long j = 1; j <= st.N[k]; ++j) logG -= log(num_traits<T>::from_int(j));
473 }
474
475 std::vector<T> range_size(K, one);
476 std::vector<T> theta(K, zero);
477 Matrix<T> smpl(opts.nsamples, K, zero);
478 std::vector<T> demand_old(K, zero);
479 const std::size_t nblocks = static_cast<std::size_t>(
480 std::floor(static_cast<double>(opts.nsamples) / static_cast<double>(opts.block) + 0.5));
481 std::size_t si = 0; // number of samples drawn so far
482
483 for (std::size_t b = 1; b <= nblocks; ++b) {
484 for (std::size_t s = 0; s < opts.block && si < opts.nsamples; ++s) {
485 for (std::size_t h = 0; h < K; ++h) {
486 // theta: this sweep's coordinates below h, the previous
487 // sweep's at and above h (zeros on the first sweep).
488 for (std::size_t j = 0; j < h; ++j) theta[j] = smpl(si, j);
489 for (std::size_t j = h; j < K; ++j)
490 theta[j] = si == 0 ? zero : smpl(si - 1, j);
491
492 const GibbsSlice<T> sl =
493 gibbs_slice(think_time, theta, testset, h, Nt, logG, opts.tol, range_size[h]);
494
495 const T u = pfqn::mc_uniform<T>(rng);
496 T cc = zero;
497 std::size_t pick = sl.grid.size();
498 for (std::size_t i = 0; i < sl.grid.size(); ++i) {
499 cc += sl.prob[i];
500 if (u < cc) {
501 pick = i;
502 break;
503 }
504 }
505 if (pick == sl.grid.size()) {
506 // MATLAB's empty-find branch: keep the current value and
507 // the running constant untouched.
508 smpl(si, h) = theta[h];
509 } else {
510 smpl(si, h) = sl.grid[pick];
511 logG = sl.logG[pick];
512 }
513 // grid-width doubling rationale: see _kb/03-api-layer.md (cpp port notes: infer)
514 range_size[h] = sl.range_size_dim * num_traits<T>::from_int(2);
515 }
516 ++si;
517 }
518
519 if (b == 2) {
520 for (std::size_t k = 0; k < K; ++k) {
521 T s = zero;
522 for (std::size_t i = opts.block; i < si; ++i) s += smpl(i, k);
523 demand_old[k] = s / num_traits<T>::from_int(static_cast<long>(si - opts.block));
524 }
525 } else if (b > 2) {
526 const std::size_t lo = (b - 1) * opts.block;
527 std::vector<T> demand_now(K, zero);
528 const T nb = num_traits<T>::from_int(static_cast<long>(si - lo));
529 const T bp1 = num_traits<T>::from_int(static_cast<long>(b + 1));
530 const T bb = num_traits<T>::from_int(static_cast<long>(b));
531 for (std::size_t k = 0; k < K; ++k) {
532 T s = zero;
533 for (std::size_t i = lo; i < si; ++i) s += smpl(i, k);
534 const T m = s / nb;
535 demand_now[k] = m / bp1 + demand_old[k] / bp1 * bb;
536 }
537 T rel = zero;
538 bool ok = true;
539 for (std::size_t k = 0; k < K; ++k) {
540 if (demand_old[k] == zero) {
541 ok = false;
542 break;
543 }
544 rel += num_abs(T((demand_now[k] - demand_old[k]) / demand_old[k]));
545 }
546 if (ok) rel = rel / num_traits<T>::from_int(static_cast<long>(K));
547 if (ok && num_traits<T>::to_double(rel) < opts.tol) break;
548 demand_old = demand_now;
549 }
550 }
551
552 // MATLAB averages the second half of the chain, dropping the last sample.
553 const std::size_t nb = si == 0 ? 0 : si - 1;
554 const std::size_t lo = static_cast<std::size_t>(
555 std::floor(static_cast<double>(nb) / 2.0 + 0.5)); // round(nb/2)+1, 0-based
556 if (nb == 0 || lo >= nb) throw NumericError("infer_gibbs: the chain produced no usable tail");
557 std::vector<T> demand(K, zero);
558 for (std::size_t k = 0; k < K; ++k) {
559 T s = zero;
560 for (std::size_t i = lo; i < nb; ++i) s += smpl(i, k) * used;
561 demand[k] = s / num_traits<T>::from_int(static_cast<long>(nb - lo));
562 }
563 return demand;
564}
565
566/** MATLAB's three-argument form, with its hard-coded budgets. */
567template <class T>
568std::vector<T> infer_gibbs(const std::vector<GibbsTrace<T>>& data, const T& nbCores, double tol,
569 pfqn::McRng& rng) {
570 GibbsOptions o;
571 o.tol = tol;
572 return infer_gibbs(data, nbCores, o, rng);
573}
574
575} // namespace infer
576} // namespace line
577
578#endif // LINE_API_INFER_INFER_GIBBS_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
static const std::size_t GIBBS_NNODES
The two nodes of the model the estimator assumes: delay, then queue.
Definition infer_gibbs.h:87
GibbsStateProbs< T > gibbs_analyse_data(const std::vector< GibbsTrace< T > > &data, std::size_t data_needed)
Empirical state distribution of the replayed traces (MATLAB's analyseData).
GibbsSlice< T > gibbs_slice(const std::vector< T > &think_time, const std::vector< T > &theta, const Matrix< long > &testset, std::size_t index, const std::vector< T > &N, const T &logG_init, double interval, const T &range_size)
The deterministic half of one coordinate update: the log normalizing constant along the grid and the ...
std::vector< T > infer_gibbs(const std::vector< GibbsTrace< T > > &data, const T &nbCores, const GibbsOptions &opts, pfqn::McRng &rng)
Estimated per-class mean demands.
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
AmvaResult< T > pfqn_bs(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >())
Bard-Schweitzer approximate MVA.
Definition pfqn_bs.h:73
T mc_uniform(McRng &g)
The same deviate materialized in the working arithmetic.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Bard-Schweitzer approximate MVA.
Randomness scaffolding shared by the Monte Carlo normalizing-constant estimators (pfqn_mci,...
MATLAB's hard-coded budgets, exposed with their MATLAB values as defaults.
std::size_t data_needed
events kept from the end of the trace
double tol
grid step and convergence tolerance
std::size_t nsamples
chain length
std::size_t likelihood_sample
test set size
std::size_t block
samples per convergence block
The deterministic content of one coordinate update.
T range_size_dim
twice the value at which the slice mass closes
std::vector< T > prob
normalized slice probability
std::vector< T > logG
log normalizing constant along the grid
std::vector< T > grid
candidate values, 0, interval, 2 interval, ...
The empirical state distribution built from the traces.
std::vector< T > prob
(ns) time fraction spent in each state
std::vector< long > N
(K) per-class population
std::vector< T > N0
(K) per-class mean number at the queue
Matrix< long > states
(ns x K*GIBBS_NNODES) population vectors, node-major
One class's trace.
Definition infer_gibbs.h:95
std::vector< T > arrival_ms
data{3,k}, arrival times in MILLISECONDS
Definition infer_gibbs.h:96
std::vector< T > respt_s
data{4,k}, response times in SECONDS
Definition infer_gibbs.h:97
std::vector< T > think_obs
data{6,k}, the think-time normalizer
Definition infer_gibbs.h:98
Matrix< T > QN
(M x R) queue length
Definition pfqn_bs.h:50