5#ifndef LINE_SOLVERS_LDES_LDES_STATS_H
6#define LINE_SOLVERS_LDES_LDES_STATS_H
47 Accum(std::size_t M, std::size_t K)
48 :
qlen(M, std::vector<double>(K, 0.0)),
49 held(M, std::vector<double>(K, 0.0)),
50 busy(M, std::vector<double>(K, 0.0)),
51 tot_qlen(M, std::vector<double>(K, 0.0)),
52 tot_busy(M, std::vector<double>(K, 0.0)),
53 last_qlen(M, std::vector<double>(K, 0.0)),
54 last_busy(M, std::vector<double>(K, 0.0)),
57 resp_sum(M, std::vector<double>(K, 0.0)),
58 resp_cnt(M, std::vector<double>(K, 0.0)),
59 completed(M, std::vector<double>(K, 0.0)),
60 arrived(M, std::vector<double>(K, 0.0)),
93 for (std::size_t r = 0; r <
busy[i].size(); ++r)
update_busy(i, r, now);
97 std::vector<std::vector<double>>
qlen;
106 std::vector<std::vector<double>>
held;
107 std::vector<std::vector<double>>
busy;
149 const std::size_t n = obs.size();
150 if (batch_size <= 0 || n <
static_cast<std::size_t
>(batch_size) * 4)
return 0;
151 const int num_batches =
static_cast<int>(n /
static_cast<std::size_t
>(batch_size));
152 std::vector<double> means(
static_cast<std::size_t
>(num_batches), 0.0);
153 for (
int j = 0; j < num_batches; ++j) {
155 for (
int i = 0; i < batch_size; ++i)
156 sum += obs[
static_cast<std::size_t
>(j * batch_size + i)];
157 means[
static_cast<std::size_t
>(j)] =
sum / batch_size;
159 double min_mser = std::numeric_limits<double>::max();
161 const int max_d = num_batches / 2;
162 for (
int d = 0; d < max_d; ++d) {
163 const int remaining = num_batches - d;
164 if (remaining < 2)
break;
166 for (
int j = d; j < num_batches; ++j) sum += means[static_cast<std::size_t>(j)];
167 const double mean =
sum / remaining;
168 double variance = 0.0;
169 for (
int j = d; j < num_batches; ++j) {
170 const double diff = means[
static_cast<std::size_t
>(j)] - mean;
171 variance += diff * diff;
173 variance /= (remaining - 1);
174 const double mser = variance / (
static_cast<double>(remaining) * remaining);
175 if (mser < min_mser) {
195 qlen(M, std::vector<std::vector<double>>(K)),
196 qt(M, std::vector<std::vector<double>>(K)),
197 bt(M, std::vector<std::vector<double>>(K)),
198 cmp(M, std::vector<std::vector<double>>(K)),
199 drp(M, std::vector<std::vector<double>>(K)),
200 last_qt(M, std::vector<double>(K, 0.0)),
201 in_mser(M, static_cast<char>(1)),
218 for (std::size_t i = 0; i <
nstations; ++i)
219 for (std::size_t r = 0; r <
nclasses; ++r) {
224 qlen[i][r].push_back(acc.
qlen[i][r]);
250 std::vector<double> aggregate(
time.size(), 0.0);
251 for (std::size_t k = 0; k <
time.size(); ++k) {
253 for (std::size_t i = 0; i <
nstations; ++i) {
255 for (std::size_t r = 0; r <
nclasses; ++r)
256 if (k <
qlen[i][r].size()) tot +=
qlen[i][r][k];
262 for (std::size_t i = 0; i <
nstations; ++i) {
264 for (std::size_t r = 0; r <
nclasses; ++r) {
266 if (b > batch) batch = b;
270 const std::size_t idx =
271 static_cast<std::size_t
>(batch) *
static_cast<std::size_t
>(
batch_size);
272 if (idx <
time.size()) {
309 if (overlap <= 0.0)
return 1.0;
310 if (overlap >= 0.5)
return 4.0 / 3.0;
311 return 1.0 + (overlap / 0.5) * (4.0 / 3.0 - 1.0);
317 const std::size_t n = obs.size();
318 const int nb = (batch_size > 0) ?
static_cast<int>(n /
static_cast<std::size_t
>(batch_size)) : 0;
319 if (nb < 2)
return s;
320 std::vector<double> means(
static_cast<std::size_t
>(nb), 0.0);
321 for (
int i = 0; i < nb; ++i) {
323 for (
int j = 0; j < batch_size; ++j)
324 sum += obs[
static_cast<std::size_t
>(i * batch_size + j)];
325 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
328 for (
int i = 0; i < nb; ++i) grand += means[static_cast<std::size_t>(i)];
331 for (
int i = 0; i < nb; ++i) {
332 const double d = means[
static_cast<std::size_t
>(i)] - grand;
336 s.
stderr_ = std::sqrt((ss / (nb - 1)) / nb);
345 const std::size_t n = obs.size();
346 if (batch_size <= 0 || n <
static_cast<std::size_t
>(batch_size) * 2)
return s;
347 int step =
static_cast<int>(batch_size * (1.0 - overlap));
348 if (step < 1) step = 1;
349 const int nb =
static_cast<int>((n -
static_cast<std::size_t
>(batch_size)) /
350 static_cast<std::size_t
>(step)) + 1;
351 if (nb < 2)
return s;
352 std::vector<double> means(
static_cast<std::size_t
>(nb), 0.0);
353 for (
int i = 0; i < nb; ++i) {
354 const std::size_t start =
static_cast<std::size_t
>(i) *
static_cast<std::size_t
>(step);
356 for (
int j = 0; j < batch_size; ++j)
357 if (start +
static_cast<std::size_t
>(j) < n)
sum += obs[start +
static_cast<std::size_t
>(j)];
358 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
361 for (
int i = 0; i < nb; ++i) grand += means[static_cast<std::size_t>(i)];
364 for (
int i = 0; i < nb; ++i) {
365 const double d = means[
static_cast<std::size_t
>(i)] - grand;
370 s.
stderr_ = std::sqrt((adj * ss / (nb - 1)) / nb);
371 s.
df =
static_cast<int>((nb - 1) / adj);
372 if (s.
df < 1) s.
df = 1;
388 static const double t90[30] = {
389 6.314, 2.920, 2.353, 2.132, 2.015, 1.943, 1.895, 1.860, 1.833, 1.812,
390 1.796, 1.782, 1.771, 1.761, 1.753, 1.746, 1.740, 1.734, 1.729, 1.725,
391 1.721, 1.717, 1.714, 1.711, 1.708, 1.706, 1.703, 1.701, 1.699, 1.697};
392 static const double t95[30] = {
393 12.706, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228,
394 2.201, 2.179, 2.160, 2.145, 2.131, 2.120, 2.110, 2.101, 2.093, 2.086,
395 2.080, 2.074, 2.069, 2.064, 2.060, 2.056, 2.052, 2.048, 2.045, 2.042};
396 static const double t99[30] = {
397 63.657, 9.925, 5.841, 4.604, 4.032, 3.707, 3.499, 3.355, 3.250, 3.169,
398 3.106, 3.055, 3.012, 2.977, 2.947, 2.921, 2.898, 2.878, 2.861, 2.845,
399 2.831, 2.819, 2.807, 2.797, 2.787, 2.779, 2.771, 2.763, 2.756, 2.750};
400 const int idx = std::min(df, 30) - 1;
401 if (idx < 0)
return 1.96;
402 if (level >= 0.99)
return t99[idx];
403 if (level >= 0.95)
return t95[idx];
427 double low_freq_frac) {
428 const std::size_t n = obs.size();
429 const int m = (batch_size > 0) ?
static_cast<int>(n /
static_cast<std::size_t
>(batch_size)) : 0;
432 std::vector<double> means(
static_cast<std::size_t
>(m), 0.0);
433 for (
int i = 0; i < m; ++i) {
435 for (
int j = 0; j < batch_size; ++j)
436 sum += obs[
static_cast<std::size_t
>(i * batch_size + j)];
437 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
440 for (
int i = 0; i < m; ++i) grand += means[static_cast<std::size_t>(i)];
443 const int nf = m / 2;
445 std::vector<double> per(
static_cast<std::size_t
>(nf), 0.0);
446 for (
int k = 1; k <= nf; ++k) {
447 double re = 0.0, im = 0.0;
448 const double f = 2.0 * 3.14159265358979323846 * k / m;
449 for (
int t = 0; t < m; ++t) {
450 const double c = means[
static_cast<std::size_t
>(t)] - grand;
451 re += c * std::cos(f * t);
452 im += c * std::sin(f * t);
454 per[
static_cast<std::size_t
>(k - 1)] = (re * re + im * im) / m;
457 int npts =
static_cast<int>(nf * low_freq_frac);
458 if (npts < 3) npts = 3;
460 for (
int k = 0; k < npts; ++k)
461 if (!(per[
static_cast<std::size_t
>(k)] > 0.0))
return bm_statistics(obs, batch_size);
467 for (
int r = 0; r < 3; ++r)
468 for (
int c = 0; c < 4; ++c) A[r][c] = 0.0;
469 for (
int k = 0; k < npts; ++k) {
470 const double f = 2.0 * 3.14159265358979323846 * (k + 1) / m;
471 const double x[3] = {1.0, f, f * f};
472 const double y = std::log(per[
static_cast<std::size_t
>(k)]);
473 for (
int r = 0; r < 3; ++r) {
474 for (
int c = 0; c < 3; ++c) A[r][c] += x[r] * x[c];
478 for (
int i = 0; i < 3; ++i) {
480 for (
int r = i + 1; r < 3; ++r)
481 if (std::fabs(A[r][i]) > std::fabs(A[piv][i])) piv = r;
482 if (!(std::fabs(A[piv][i]) > 1e-300))
return bm_statistics(obs, batch_size);
484 for (
int c = 0; c < 4; ++c) std::swap(A[i][c], A[piv][c]);
485 for (
int r = 0; r < 3; ++r) {
486 if (r == i)
continue;
487 const double fct = A[r][i] / A[i][i];
488 for (
int c = i; c < 4; ++c) A[r][c] -= fct * A[i][c];
491 const double b0 = A[0][3] / A[0][0];
492 const double s0 = std::exp(b0);
493 if (!(s0 > 0.0) || !std::isfinite(s0))
return bm_statistics(obs, batch_size);
497 st.
stderr_ = std::sqrt(2.0 * 3.14159265358979323846 * s0 / m);
499 if (st.
df < 1) st.
df = 1;
529 void init(std::size_t M, std::size_t K,
const LdesOptions& o, std::uint64_t max_events) {
531 if (!enabled_)
return;
534 interval_ = (o.
cnvgchk > 0) ?
static_cast<std::uint64_t
>(o.
cnvgchk)
535 : std::max<std::uint64_t>(1, max_events / 50);
537 q_.assign(M, std::vector<std::vector<double>>(K));
538 u_.assign(M, std::vector<std::vector<double>>(K));
539 r_.assign(M, std::vector<std::vector<double>>(K));
540 t_.assign(M, std::vector<std::vector<double>>(K));
541 start_qt_.assign(M, std::vector<double>(K, 0.0));
542 start_bt_.assign(M, std::vector<double>(K, 0.0));
543 start_cmp_.assign(M, std::vector<double>(K, 0.0));
544 start_rsum_.assign(M, std::vector<double>(K, 0.0));
545 start_rcnt_.assign(M, std::vector<double>(K, 0.0));
551 std::uint64_t
interval()
const {
return interval_; }
555 if (!enabled_)
return;
556 const double dur = now - batch_start_;
558 reset_batch(acc, now);
561 for (std::size_t i = 0; i < nstations_; ++i)
562 for (std::size_t k = 0; k < nclasses_; ++k) {
563 const double ql = (acc.
tot_qlen[i][k] - start_qt_[i][k]) / dur;
564 q_[i][k].push_back(ql);
567 :
static_cast<double>(nservers[i] > 0 ? nservers[i] : 1);
568 u_[i][k].push_back((acc.
tot_busy[i][k] - start_bt_[i][k]) / (dur * c));
569 t_[i][k].push_back((acc.
completed[i][k] - start_cmp_[i][k]) / dur);
570 const double dn = acc.
resp_cnt[i][k] - start_rcnt_[i][k];
571 r_[i][k].push_back(dn > 0.0 ? (acc.
resp_sum[i][k] - start_rsum_[i][k]) / dn : 0.0);
573 reset_batch(acc, now);
577 bool converged(
const std::vector<std::vector<bool>>& off)
const {
578 if (!enabled_)
return false;
579 if (nstations_ == 0 || nclasses_ == 0)
return false;
580 if (
static_cast<int>(q_[0][0].size()) < min_batches_)
return false;
581 for (std::size_t i = 0; i < nstations_; ++i)
582 for (std::size_t k = 0; k < nclasses_; ++k) {
583 if (off[i][k])
continue;
584 if (!metric_ok(q_[i][k]))
return false;
585 if (!metric_ok(u_[i][k]))
return false;
586 if (!metric_ok(t_[i][k]))
return false;
587 bool any_positive =
false;
588 for (
double v : r_[i][k])
593 if (any_positive && !metric_ok(r_[i][k]))
return false;
598 int batches()
const {
return (nstations_ && nclasses_) ?
static_cast<int>(q_[0][0].size()) : 0; }
601 void reset_batch(
const Accum& acc,
double now) {
603 for (std::size_t i = 0; i < nstations_; ++i)
604 for (std::size_t k = 0; k < nclasses_; ++k) {
605 start_qt_[i][k] = acc.
tot_qlen[i][k];
606 start_bt_[i][k] = acc.
tot_busy[i][k];
608 start_rsum_[i][k] = acc.
resp_sum[i][k];
609 start_rcnt_[i][k] = acc.
resp_cnt[i][k];
613 bool metric_ok(
const std::vector<double>& b)
const {
614 const std::size_t n = b.size();
615 if (n < 2)
return false;
617 for (
double x : b)
sum += x;
618 const double mean =
sum / n;
620 if (std::fabs(mean) < 1e-12)
return true;
622 for (
double x : b) var += (x - mean) * (x - mean);
624 const double se = std::sqrt(var / n);
625 const double half =
t_critical(level_,
static_cast<int>(n) - 1) * se;
626 return half / std::fabs(mean) <= tol_;
629 bool enabled_ =
false;
630 double tol_ = 0.05, level_ = 0.95, batch_start_ = 0.0;
631 int min_batches_ = 20;
632 std::uint64_t interval_ = 1;
633 std::size_t nstations_ = 0, nclasses_ = 0;
634 std::vector<std::vector<std::vector<double>>> q_, u_, r_, t_;
635 std::vector<std::vector<double>> start_qt_, start_bt_, start_cmp_, start_rsum_, start_rcnt_;
665 const std::size_t t0 =
tr.applied ?
tr.index : 0;
667 for (std::size_t i = 0; i < M; ++i) {
668 for (std::size_t r = 0; r < K; ++r) {
669 if (t0 >= obs.
qlen[i][r].size())
continue;
670 const std::vector<double> q(obs.
qlen[i][r].begin() +
static_cast<std::ptrdiff_t
>(t0),
671 obs.
qlen[i][r].end());
672 if (q.size() >=
static_cast<std::size_t
>(o.
ciminobs)) {
673 const int b = std::max(o.
ciminbatch,
static_cast<int>(std::sqrt(
674 static_cast<double>(q.size()))));
679 std::vector<double> rates;
680 for (std::size_t k = t0 + 1; k < obs.
cmp[i][r].size(); ++k) {
681 const double dt = obs.
time[k] - obs.
time[k - 1];
682 if (dt > 0.0) rates.push_back((obs.
cmp[i][r][k] - obs.
cmp[i][r][k - 1]) / dt);
684 if (rates.size() >=
static_cast<std::size_t
>(o.
ciminobs)) {
685 const int b = std::max(o.
ciminbatch,
static_cast<int>(std::sqrt(
686 static_cast<double>(rates.size()))));
690 res.
TNCI(i, r) = half;
691 if (res.
TN.
rows() > i && res.
RN.
rows() > i && res.
UN(i, r) > 0.0 &&
693 res.
UNCI(i, r) = half * res.
UN(i, r) / res.
TN(i, r);
Convergence-based stopping: batch the four metrics and stop once EVERY active (station,...
bool converged(const std::vector< std::vector< bool > > &off) const
True once every active pair has reached the tolerance on all four metrics.
std::uint64_t interval() const
void init(std::size_t M, std::size_t K, const LdesOptions &o, std::uint64_t max_events)
void finalize_batch(const Accum &acc, const std::vector< std::size_t > &nservers, double now)
Close the current batch at now and record one batch mean per metric.
The option and result records of SolverLDES, the discrete-event simulator.
Dense matrix and non-owning view.
StatTriple bm_statistics(const std::vector< double > &obs, int batch_size)
Non-overlapping batch means.
StatTriple spectral_statistics(const std::vector< double > &obs, int batch_size, double low_freq_frac)
Heidelberger-Welch spectral estimate of the variance of the sample mean.
void batch_means_ci(const Observations &obs, const Truncation &tr, const LdesOptions &o, LdesResult &res)
Fill the half-width matrices of res from the post-warmup observation series.
StatTriple ci_statistics(const std::vector< double > &obs, int batch_size, const LdesOptions &o)
Dispatch on cimethod.
StatTriple obm_statistics(const std::vector< double > &obs, int batch_size, double overlap)
Overlapping batch means.
double t_critical(double level, int df)
The two-sided t critical value, from the REFERENCE'S TABLE.
double overlap_adjustment(double overlap)
The variance inflation of OVERLAPPING batch means.
int mser5_truncation(const std::vector< double > &obs, int batch_size)
MSER-5 truncation point over a series of observations, in BATCHES.
RunLengthPlan< T > sim_runlength_plan(const Matrix< T > &means, const Matrix< T > &ciHalfWidth, const T &samplesUsed, const T &relPrecision=num_traits< T >::from_rational(1, 20), const T &confidence=num_traits< T >::from_rational(19, 20))
How long a simulation run should have been, from the one it already did.
Conservation laws of a layered queueing network, enumerated from its structure.
The knobs of one LDES run.
int ciminbatch
–ciminbatch
double spectral_low_freq_frac
–spectrallowfreqfrac
double confint
Confidence level of the reported half-widths.
double obmoverlap
–obmoverlap; 0 reduces OBM to plain batch means
double run_length_plan_precision
options.config.runLengthPlan: ask for the run length this run SHOULD have had, for a target relative ...
std::string cimethod
–cimethod: obm | bm | spectral | none
int cnvgchk
–cnvgchk, events between checks; 0 = samples/50
int ciminobs
–ciminobs, below which no CI is reported
std::size_t events
0 = not given; overrides samples when set
int cnvgbatch
–cnvgbatch, batches before the first check
std::size_t samples
-s, service-completion budget
One ldes-result document, parsed.
sim::RunLengthPlan< double > run_length_plan
bool has_run_length_plan
options.config.runLengthPlan: the run length the caller would need for the precision they asked for,...
long long total_simulated_events
The running per-(station, class) integrals and tallies.
std::vector< std::vector< double > > held
Callers parked on their server awaiting a synchronous REPLY (sn.syncreply), per (station,...
Accum(std::size_t M, std::size_t K)
void update_busy(std::size_t i, std::size_t r, double now)
Advance the busy-server integral of (i,r) to now.
std::vector< std::vector< double > > busy
std::vector< double > busy_scale
The speed each station is running at right now (load dependence only), and the peak capacity that nor...
std::vector< std::vector< double > > last_qlen
std::vector< std::vector< double > > resp_cnt
std::vector< std::vector< double > > qlen
std::vector< std::vector< double > > tot_qlen
std::vector< std::vector< double > > completed
std::vector< std::vector< double > > resp_sum
std::vector< std::vector< double > > join_dropped
Siblings a QUORUM Join discarded, per (station, class): a sibling that reaches the Join after its par...
void update_qlen(std::size_t i, std::size_t r, double now)
Advance the queue-length integral of (i,r) to now.
void set_busy_scale(std::size_t i, double v, double now)
Install the load-dependent speed station i runs at from now on.
std::vector< std::vector< double > > tot_busy
std::vector< std::vector< double > > arrived
Jobs that ARRIVED at each (station, class), which is not what completed counts: the reference reports...
std::vector< std::vector< double > > last_busy
std::vector< double > util_peak
The event-spaced observation series MSER-5 and the CI both read.
Truncation truncate() const
The truncation point, on the AGGREGATE queue length first.
std::vector< std::vector< double > > last_qt
std::vector< std::vector< std::vector< double > > > bt
std::vector< std::vector< std::vector< double > > > qlen
std::vector< std::vector< std::vector< double > > > cmp
std::vector< char > in_mser
Whether station i feeds the truncation criterion; every one is still recorded.
std::vector< double > time
void collect(const Accum &acc, double now)
Record one observation.
std::vector< std::vector< std::vector< double > > > drp
std::vector< std::vector< std::vector< double > > > qt
Observations(std::size_t M, std::size_t K, bool mser_on, int batch)
Grand mean, standard error and degrees of freedom of a batch-means estimate.
Where the warmup ended, and whether a truncation was applied at all.
double warmup_end
the instant of that observation
std::size_t index
observation index of the truncation point