5#ifndef LINE_API_INFER_INFER_GIBBS_H
6#define LINE_API_INFER_INFER_GIBBS_H
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;
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");
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) {
181 a.t = data[k].arrival_ms[i];
186 for (std::size_t i = 0; i < n; ++i) {
188 d.t = T(data[k].arrival_ms[i] + data[k].respt_s[i] * thousand);
194 const std::size_t total = ev.size();
195 if (total == 0)
throw InputError(
"gibbs_analyse_data: empty traces");
198 std::stable_sort(ev.begin(), ev.end(),
199 [](
const Ev& a,
const Ev& b) { return a.t < b.t; });
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;
208 count(i + 1, from * K + k) -= 1;
209 count(i + 1, to * K + k) += 1;
214 for (std::size_t k = 0; k < K; ++k) {
216 for (std::size_t i = 0; i < total; ++i)
218 if (count(i, nd * K + k) > m) m = count(i, nd * K + k);
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];
225 std::size_t burnin = 0;
226 if (data_needed != 0) {
227 if (total == data_needed)
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;
236 std::map<std::vector<long>, T> acc;
237 for (std::size_t i = burnin; i < total; ++i) {
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);
243 acc.insert(std::make_pair(key, dt));
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");
252 out.
prob.assign(acc.size(), zero);
254 for (
typename std::map<std::vector<long>, T>::const_iterator it = acc.begin();
255 it != acc.end(); ++it, ++r) {
257 out.
prob[r] = it->second / obs_length;
260 out.
N0.assign(K, zero);
261 for (std::size_t k = 0; k < K; ++k) {
263 for (std::size_t i = 0; i < out.
prob.size(); ++i)
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");
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");
299 throw InputError(
"gibbs_slice: testset has the wrong width");
303 const T eps = detail::gibbs_eps<T>();
307 const std::size_t n =
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)
313 out.
logG.assign(n, zero);
318 for (std::size_t i = 0; i < n && !found; ++i)
319 if (out.
grid[i] == theta[index]) {
326 for (std::size_t j = 0; j < K; ++j) L(0, j) = theta[j];
327 out.
logG[ip] = logG_init;
330 pfqn::pfqn_bs(L, N, think_time, std::vector<pfqn::AmvaSched>());
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,
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));
341 L(0, index) = theta[index];
342 res =
pfqn::pfqn_bs(L, N, think_time, std::vector<pfqn::AmvaSched>());
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,
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));
357 for (std::size_t i = 0; i < testset.
rows(); ++i)
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");
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;
368 for (std::size_t i = 1; i < n; ++i)
369 if (
lp[i] > mx) mx =
lp[i];
370 out.
prob.assign(n, zero);
372 for (std::size_t i = 0; i < n; ++i) {
373 out.
prob[i] = exp(T(
lp[i] - mx));
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;
383 for (std::size_t i = 0; i < n; ++i) {
406 "infer_gibbs requires transcendental arithmetic: it samples a slice of a "
407 "log density built from an iteratively integrated normalizing constant");
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");
414 if (!(opts.
tol > 0.0))
throw InputError(
"infer_gibbs: the tolerance must be positive");
420 const std::size_t ns = st.
prob.size();
424 for (std::size_t i = 0; i < ns; ++i) {
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];
429 const T denom = one - st.
prob[ns - 1];
430 if (denom == zero)
throw NumericError(
"infer_gibbs: the record holds a single state");
433 std::vector<T> Nt(K, zero), think_time(K, zero);
434 for (std::size_t k = 0; k < K; ++k) {
436 if (data[k].think_obs.empty())
437 throw InputError(
"infer_gibbs: a class has no think-time observations");
439 for (std::size_t i = 0; i < data[k].think_obs.size(); ++i) s += data[k].think_obs[i];
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");
448 std::vector<T> cum(ns, zero);
450 for (std::size_t i = 0; i < ns; ++i) {
457 std::size_t pick = ns;
458 for (std::size_t i = 0; i < ns; ++i)
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);
470 for (std::size_t k = 0; k < K; ++k) {
471 logG += Nt[k] * log(think_time[k]);
475 std::vector<T> range_size(K, one);
476 std::vector<T> theta(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));
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) {
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);
493 gibbs_slice(think_time, theta, testset, h, Nt, logG, opts.
tol, range_size[h]);
497 std::size_t pick = sl.
grid.size();
498 for (std::size_t i = 0; i < sl.
grid.size(); ++i) {
505 if (pick == sl.
grid.size()) {
508 smpl(si, h) = theta[h];
510 smpl(si, h) = sl.
grid[pick];
511 logG = sl.
logG[pick];
520 for (std::size_t k = 0; k < K; ++k) {
522 for (std::size_t i = opts.
block; i < si; ++i) s += smpl(i, k);
526 const std::size_t lo = (b - 1) * opts.
block;
527 std::vector<T> demand_now(K, zero);
531 for (std::size_t k = 0; k < K; ++k) {
533 for (std::size_t i = lo; i < si; ++i) s += smpl(i, k);
535 demand_now[k] = m / bp1 + demand_old[k] / bp1 * bb;
539 for (std::size_t k = 0; k < K; ++k) {
540 if (demand_old[k] == zero) {
544 rel +=
num_abs(T((demand_now[k] - demand_old[k]) / demand_old[k]));
548 demand_old = demand_now;
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));
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) {
560 for (std::size_t i = lo; i < nb; ++i) s += smpl(i, k) * used;
NumericError(const std::string &what)
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.
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.
T mc_uniform(McRng &g)
The same deviate materialized in the working arithmetic.
Conservation laws of a layered queueing network, enumerated from its structure.
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
std::vector< T > arrival_ms
data{3,k}, arrival times in MILLISECONDS
std::vector< T > respt_s
data{4,k}, response times in SECONDS
std::vector< T > think_obs
data{6,k}, the think-time normalizer
Matrix< T > QN
(M x R) queue length