5#ifndef LINE_SOLVERS_LDES_LDES_STATS_H
6#define LINE_SOLVERS_LDES_LDES_STATS_H
48 Accum(std::size_t M, std::size_t K)
49 :
qlen(M, std::vector<double>(K, 0.0)),
50 held(M, std::vector<double>(K, 0.0)),
51 busy(M, std::vector<double>(K, 0.0)),
52 tot_qlen(M, std::vector<double>(K, 0.0)),
53 tot_busy(M, std::vector<double>(K, 0.0)),
54 last_qlen(M, std::vector<double>(K, 0.0)),
55 last_busy(M, std::vector<double>(K, 0.0)),
58 resp_sum(M, std::vector<double>(K, 0.0)),
59 resp_cnt(M, std::vector<double>(K, 0.0)),
60 resp_sq(M, std::vector<double>(K, 0.0)),
61 completed(M, std::vector<double>(K, 0.0)),
62 arrived(M, std::vector<double>(K, 0.0)),
95 for (std::size_t r = 0; r <
busy[i].size(); ++r)
update_busy(i, r, now);
99 std::vector<std::vector<double>>
qlen;
108 std::vector<std::vector<double>>
held;
109 std::vector<std::vector<double>>
busy;
160 const std::size_t n = obs.size();
161 if (batch_size <= 0 || n <
static_cast<std::size_t
>(batch_size) * 4)
return 0;
162 const int num_batches =
static_cast<int>(n /
static_cast<std::size_t
>(batch_size));
163 std::vector<double> means(
static_cast<std::size_t
>(num_batches), 0.0);
164 for (
int j = 0; j < num_batches; ++j) {
166 for (
int i = 0; i < batch_size; ++i)
167 sum += obs[
static_cast<std::size_t
>(j * batch_size + i)];
168 means[
static_cast<std::size_t
>(j)] =
sum / batch_size;
170 double min_mser = std::numeric_limits<double>::max();
172 const int max_d = num_batches / 2;
173 for (
int d = 0; d < max_d; ++d) {
174 const int remaining = num_batches - d;
175 if (remaining < 2)
break;
177 for (
int j = d; j < num_batches; ++j) sum += means[static_cast<std::size_t>(j)];
178 const double mean =
sum / remaining;
179 double variance = 0.0;
180 for (
int j = d; j < num_batches; ++j) {
181 const double diff = means[
static_cast<std::size_t
>(j)] - mean;
182 variance += diff * diff;
184 variance /= (remaining - 1);
185 const double mser = variance / (
static_cast<double>(remaining) * remaining);
186 if (mser < min_mser) {
206 qlen(M, std::vector<std::vector<double>>(K)),
207 qt(M, std::vector<std::vector<double>>(K)),
208 bt(M, std::vector<std::vector<double>>(K)),
209 cmp(M, std::vector<std::vector<double>>(K)),
210 drp(M, std::vector<std::vector<double>>(K)),
211 arv(M, std::vector<std::vector<double>>(K)),
212 last_qt(M, std::vector<double>(K, 0.0)),
213 in_mser(M, static_cast<char>(1)),
230 for (std::size_t i = 0; i <
nstations; ++i)
231 for (std::size_t r = 0; r <
nclasses; ++r) {
236 qlen[i][r].push_back(acc.
qlen[i][r]);
263 std::vector<double> aggregate(
time.size(), 0.0);
264 for (std::size_t k = 0; k <
time.size(); ++k) {
266 for (std::size_t i = 0; i <
nstations; ++i) {
268 for (std::size_t r = 0; r <
nclasses; ++r)
269 if (k <
qlen[i][r].size()) tot +=
qlen[i][r][k];
275 for (std::size_t i = 0; i <
nstations; ++i) {
277 for (std::size_t r = 0; r <
nclasses; ++r) {
279 if (b > batch) batch = b;
283 const std::size_t idx =
284 static_cast<std::size_t
>(batch) *
static_cast<std::size_t
>(
batch_size);
285 if (idx <
time.size()) {
322 if (overlap <= 0.0)
return 1.0;
323 if (overlap >= 0.5)
return 4.0 / 3.0;
324 return 1.0 + (overlap / 0.5) * (4.0 / 3.0 - 1.0);
330 const std::size_t n = obs.size();
331 const int nb = (batch_size > 0) ?
static_cast<int>(n /
static_cast<std::size_t
>(batch_size)) : 0;
332 if (nb < 2)
return s;
333 std::vector<double> means(
static_cast<std::size_t
>(nb), 0.0);
334 for (
int i = 0; i < nb; ++i) {
336 for (
int j = 0; j < batch_size; ++j)
337 sum += obs[
static_cast<std::size_t
>(i * batch_size + j)];
338 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
341 for (
int i = 0; i < nb; ++i) grand += means[static_cast<std::size_t>(i)];
344 for (
int i = 0; i < nb; ++i) {
345 const double d = means[
static_cast<std::size_t
>(i)] - grand;
349 s.
stderr_ = std::sqrt((ss / (nb - 1)) / nb);
358 const std::size_t n = obs.size();
359 if (batch_size <= 0 || n <
static_cast<std::size_t
>(batch_size) * 2)
return s;
360 int step =
static_cast<int>(batch_size * (1.0 - overlap));
361 if (step < 1) step = 1;
362 const int nb =
static_cast<int>((n -
static_cast<std::size_t
>(batch_size)) /
363 static_cast<std::size_t
>(step)) + 1;
364 if (nb < 2)
return s;
365 std::vector<double> means(
static_cast<std::size_t
>(nb), 0.0);
366 for (
int i = 0; i < nb; ++i) {
367 const std::size_t start =
static_cast<std::size_t
>(i) *
static_cast<std::size_t
>(step);
369 for (
int j = 0; j < batch_size; ++j)
370 if (start +
static_cast<std::size_t
>(j) < n)
sum += obs[start +
static_cast<std::size_t
>(j)];
371 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
374 for (
int i = 0; i < nb; ++i) grand += means[static_cast<std::size_t>(i)];
377 for (
int i = 0; i < nb; ++i) {
378 const double d = means[
static_cast<std::size_t
>(i)] - grand;
383 s.
stderr_ = std::sqrt((adj * ss / (nb - 1)) / nb);
384 s.
df =
static_cast<int>((nb - 1) / adj);
385 if (s.
df < 1) s.
df = 1;
399 const double p = 0.5 + 0.5 * level;
424 double low_freq_frac) {
425 const std::size_t n = obs.size();
426 const int m = (batch_size > 0) ?
static_cast<int>(n /
static_cast<std::size_t
>(batch_size)) : 0;
429 std::vector<double> means(
static_cast<std::size_t
>(m), 0.0);
430 for (
int i = 0; i < m; ++i) {
432 for (
int j = 0; j < batch_size; ++j)
433 sum += obs[
static_cast<std::size_t
>(i * batch_size + j)];
434 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
437 for (
int i = 0; i < m; ++i) grand += means[static_cast<std::size_t>(i)];
440 const int nf = m / 2;
442 std::vector<double> per(
static_cast<std::size_t
>(nf), 0.0);
443 for (
int k = 1; k <= nf; ++k) {
444 double re = 0.0, im = 0.0;
445 const double f = 2.0 * 3.14159265358979323846 * k / m;
446 for (
int t = 0; t < m; ++t) {
447 const double c = means[
static_cast<std::size_t
>(t)] - grand;
448 re += c * std::cos(f * t);
449 im += c * std::sin(f * t);
451 per[
static_cast<std::size_t
>(k - 1)] = (re * re + im * im) / m;
454 int npts =
static_cast<int>(nf * low_freq_frac);
455 if (npts < 3) npts = 3;
457 for (
int k = 0; k < npts; ++k)
458 if (!(per[
static_cast<std::size_t
>(k)] > 0.0))
return bm_statistics(obs, batch_size);
464 for (
int r = 0; r < 3; ++r)
465 for (
int c = 0; c < 4; ++c) A[r][c] = 0.0;
466 for (
int k = 0; k < npts; ++k) {
467 const double f = 2.0 * 3.14159265358979323846 * (k + 1) / m;
468 const double x[3] = {1.0, f, f * f};
469 const double y = std::log(per[
static_cast<std::size_t
>(k)]);
470 for (
int r = 0; r < 3; ++r) {
471 for (
int c = 0; c < 3; ++c) A[r][c] += x[r] * x[c];
475 for (
int i = 0; i < 3; ++i) {
477 for (
int r = i + 1; r < 3; ++r)
478 if (std::fabs(A[r][i]) > std::fabs(A[piv][i])) piv = r;
479 if (!(std::fabs(A[piv][i]) > 1e-300))
return bm_statistics(obs, batch_size);
481 for (
int c = 0; c < 4; ++c) std::swap(A[i][c], A[piv][c]);
482 for (
int r = 0; r < 3; ++r) {
483 if (r == i)
continue;
484 const double fct = A[r][i] / A[i][i];
485 for (
int c = i; c < 4; ++c) A[r][c] -= fct * A[i][c];
488 const double b0 = A[0][3] / A[0][0];
489 const double s0 = std::exp(b0);
490 if (!(s0 > 0.0) || !std::isfinite(s0))
return bm_statistics(obs, batch_size);
494 st.
stderr_ = std::sqrt(2.0 * 3.14159265358979323846 * s0 / m);
496 if (st.
df < 1) st.
df = 1;
526 void init(std::size_t M, std::size_t K,
const LdesOptions& o, std::uint64_t max_events) {
528 if (!enabled_)
return;
531 interval_ = (o.
cnvgchk > 0) ?
static_cast<std::uint64_t
>(o.
cnvgchk)
532 : std::max<std::uint64_t>(1, max_events / 50);
534 q_.assign(M, std::vector<std::vector<double>>(K));
535 u_.assign(M, std::vector<std::vector<double>>(K));
536 r_.assign(M, std::vector<std::vector<double>>(K));
537 t_.assign(M, std::vector<std::vector<double>>(K));
538 start_qt_.assign(M, std::vector<double>(K, 0.0));
539 start_bt_.assign(M, std::vector<double>(K, 0.0));
540 start_cmp_.assign(M, std::vector<double>(K, 0.0));
541 start_rsum_.assign(M, std::vector<double>(K, 0.0));
542 start_rcnt_.assign(M, std::vector<double>(K, 0.0));
548 std::uint64_t
interval()
const {
return interval_; }
552 if (!enabled_)
return;
553 const double dur = now - batch_start_;
555 reset_batch(acc, now);
558 for (std::size_t i = 0; i < nstations_; ++i)
559 for (std::size_t k = 0; k < nclasses_; ++k) {
560 const double ql = (acc.
tot_qlen[i][k] - start_qt_[i][k]) / dur;
561 q_[i][k].push_back(ql);
564 :
static_cast<double>(nservers[i] > 0 ? nservers[i] : 1);
565 u_[i][k].push_back((acc.
tot_busy[i][k] - start_bt_[i][k]) / (dur * c));
566 t_[i][k].push_back((acc.
completed[i][k] - start_cmp_[i][k]) / dur);
567 const double dn = acc.
resp_cnt[i][k] - start_rcnt_[i][k];
568 r_[i][k].push_back(dn > 0.0 ? (acc.
resp_sum[i][k] - start_rsum_[i][k]) / dn : 0.0);
570 reset_batch(acc, now);
574 bool converged(
const std::vector<std::vector<bool>>& off)
const {
575 if (!enabled_)
return false;
576 if (nstations_ == 0 || nclasses_ == 0)
return false;
577 if (
static_cast<int>(q_[0][0].size()) < min_batches_)
return false;
578 for (std::size_t i = 0; i < nstations_; ++i)
579 for (std::size_t k = 0; k < nclasses_; ++k) {
580 if (off[i][k])
continue;
581 if (!metric_ok(q_[i][k]))
return false;
582 if (!metric_ok(u_[i][k]))
return false;
583 if (!metric_ok(t_[i][k]))
return false;
584 bool any_positive =
false;
585 for (
double v : r_[i][k])
590 if (any_positive && !metric_ok(r_[i][k]))
return false;
595 int batches()
const {
return (nstations_ && nclasses_) ?
static_cast<int>(q_[0][0].size()) : 0; }
598 void reset_batch(
const Accum& acc,
double now) {
600 for (std::size_t i = 0; i < nstations_; ++i)
601 for (std::size_t k = 0; k < nclasses_; ++k) {
602 start_qt_[i][k] = acc.
tot_qlen[i][k];
603 start_bt_[i][k] = acc.
tot_busy[i][k];
605 start_rsum_[i][k] = acc.
resp_sum[i][k];
606 start_rcnt_[i][k] = acc.
resp_cnt[i][k];
610 bool metric_ok(
const std::vector<double>& b)
const {
611 const std::size_t n = b.size();
612 if (n < 2)
return false;
614 for (
double x : b)
sum += x;
615 const double mean =
sum / n;
617 if (std::fabs(mean) < 1e-12)
return true;
619 for (
double x : b) var += (x - mean) * (x - mean);
621 const double se = std::sqrt(var / n);
622 const double half =
t_critical(level_,
static_cast<int>(n) - 1) * se;
623 return half / std::fabs(mean) <= tol_;
626 bool enabled_ =
false;
627 double tol_ = 0.05, level_ = 0.95, batch_start_ = 0.0;
628 int min_batches_ = 20;
629 std::uint64_t interval_ = 1;
630 std::size_t nstations_ = 0, nclasses_ = 0;
631 std::vector<std::vector<std::vector<double>>> q_, u_, r_, t_;
632 std::vector<std::vector<double>> start_qt_, start_bt_, start_cmp_, start_rsum_, start_rcnt_;
660 const std::vector<std::vector<bool>>* lost_unserved =
nullptr) {
667 const std::size_t t0 =
tr.applied ?
tr.index : 0;
669 for (std::size_t i = 0; i < M; ++i) {
670 for (std::size_t r = 0; r < K; ++r) {
671 if (t0 >= obs.
qlen[i][r].size())
continue;
672 const std::vector<double> q(obs.
qlen[i][r].begin() +
static_cast<std::ptrdiff_t
>(t0),
673 obs.
qlen[i][r].end());
674 if (q.size() >=
static_cast<std::size_t
>(o.
ciminobs)) {
675 const int b = std::max(o.
ciminbatch,
static_cast<int>(std::sqrt(
676 static_cast<double>(q.size()))));
681 const bool little = lost_unserved !=
nullptr && (*lost_unserved)[i][r];
682 const double n = acc.
resp_cnt[i][r];
683 if (!little && n > 30.0) {
689 std::vector<double> rates;
690 for (std::size_t k = t0 + 1; k < obs.
cmp[i][r].size(); ++k) {
691 const double dt = obs.
time[k] - obs.
time[k - 1];
692 if (dt > 0.0) rates.push_back((obs.
cmp[i][r][k] - obs.
cmp[i][r][k - 1]) / dt);
694 if (rates.size() >=
static_cast<std::size_t
>(o.
ciminobs)) {
695 const int b = std::max(o.
ciminbatch,
static_cast<int>(std::sqrt(
696 static_cast<double>(rates.size()))));
700 res.
TNCI(i, r) = half;
701 if (u_law.
rows() > i && u_law(i, r) > 0.0) res.
UNCI(i, r) = half * u_law(i, r);
706 if (little && res.
QN(i, r) > 0.0 && res.
TN(i, r) > 0.0) {
707 const double rq = res.
QNCI(i, r) / res.
QN(i, r), rt = res.
TNCI(i, r) / res.
TN(i, r);
708 res.
RNCI(i, r) = (res.
QN(i, r) / res.
TN(i, r)) * std::sqrt(rq * rq + rt * rt);
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.
StatTriple ci_statistics(const std::vector< double > &obs, int batch_size, const LdesOptions &o)
Dispatch on cimethod.
void batch_means_ci(const Observations &obs, const Truncation &tr, const Accum &acc, const LdesOptions &o, LdesResult &res, const Matrix< double > &u_law, const std::vector< std::vector< bool > > *lost_unserved=nullptr)
Fill the half-width matrices of res from the post-warmup observation series.
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 at confidence level with df degrees of freedom: the EXACT Student-t qu...
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.
double sim_norminv(double p)
Standard normal quantile function.
double sim_tinv(double p, double nu)
Quantile function of Student's t distribution.
Conservation laws of a layered queueing network, enumerated from its structure.
Normal and Student t quantiles used by the output-analysis routines.
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 > > resp_sq
Sum of squared response times, for the iid interval the reference takes off its response-time Tally.
void tally_resp(std::size_t i, std::size_t r, double x)
Tally one response time x at (i,r): the sum, its square and the count move together.
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)
std::vector< std::vector< std::vector< double > > > arv
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