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 busy(M, std::vector<double>(K, 0.0)),
50 tot_qlen(M, std::vector<double>(K, 0.0)),
51 tot_busy(M, std::vector<double>(K, 0.0)),
52 last_qlen(M, std::vector<double>(K, 0.0)),
53 last_busy(M, std::vector<double>(K, 0.0)),
56 resp_sum(M, std::vector<double>(K, 0.0)),
57 resp_cnt(M, std::vector<double>(K, 0.0)),
58 completed(M, std::vector<double>(K, 0.0)),
59 arrived(M, std::vector<double>(K, 0.0)),
92 for (std::size_t r = 0; r <
busy[i].size(); ++r)
update_busy(i, r, now);
138 const std::size_t n = obs.size();
139 if (batch_size <= 0 || n <
static_cast<std::size_t
>(batch_size) * 4)
return 0;
140 const int num_batches =
static_cast<int>(n /
static_cast<std::size_t
>(batch_size));
141 std::vector<double> means(
static_cast<std::size_t
>(num_batches), 0.0);
142 for (
int j = 0; j < num_batches; ++j) {
144 for (
int i = 0; i < batch_size; ++i)
145 sum += obs[
static_cast<std::size_t
>(j * batch_size + i)];
146 means[
static_cast<std::size_t
>(j)] =
sum / batch_size;
148 double min_mser = std::numeric_limits<double>::max();
150 const int max_d = num_batches / 2;
151 for (
int d = 0; d < max_d; ++d) {
152 const int remaining = num_batches - d;
153 if (remaining < 2)
break;
155 for (
int j = d; j < num_batches; ++j) sum += means[static_cast<std::size_t>(j)];
156 const double mean =
sum / remaining;
157 double variance = 0.0;
158 for (
int j = d; j < num_batches; ++j) {
159 const double diff = means[
static_cast<std::size_t
>(j)] - mean;
160 variance += diff * diff;
162 variance /= (remaining - 1);
163 const double mser = variance / (
static_cast<double>(remaining) * remaining);
164 if (mser < min_mser) {
184 qlen(M, std::vector<std::vector<double>>(K)),
185 qt(M, std::vector<std::vector<double>>(K)),
186 bt(M, std::vector<std::vector<double>>(K)),
187 cmp(M, std::vector<std::vector<double>>(K)),
188 drp(M, std::vector<std::vector<double>>(K)),
189 last_qt(M, std::vector<double>(K, 0.0)),
190 in_mser(M, static_cast<char>(1)),
207 for (std::size_t i = 0; i <
nstations; ++i)
208 for (std::size_t r = 0; r <
nclasses; ++r) {
213 qlen[i][r].push_back(acc.
qlen[i][r]);
239 std::vector<double> aggregate(
time.size(), 0.0);
240 for (std::size_t k = 0; k <
time.size(); ++k) {
242 for (std::size_t i = 0; i <
nstations; ++i) {
244 for (std::size_t r = 0; r <
nclasses; ++r)
245 if (k <
qlen[i][r].size()) tot +=
qlen[i][r][k];
251 for (std::size_t i = 0; i <
nstations; ++i) {
253 for (std::size_t r = 0; r <
nclasses; ++r) {
255 if (b > batch) batch = b;
259 const std::size_t idx =
260 static_cast<std::size_t
>(batch) *
static_cast<std::size_t
>(
batch_size);
261 if (idx <
time.size()) {
298 if (overlap <= 0.0)
return 1.0;
299 if (overlap >= 0.5)
return 4.0 / 3.0;
300 return 1.0 + (overlap / 0.5) * (4.0 / 3.0 - 1.0);
306 const std::size_t n = obs.size();
307 const int nb = (batch_size > 0) ?
static_cast<int>(n /
static_cast<std::size_t
>(batch_size)) : 0;
308 if (nb < 2)
return s;
309 std::vector<double> means(
static_cast<std::size_t
>(nb), 0.0);
310 for (
int i = 0; i < nb; ++i) {
312 for (
int j = 0; j < batch_size; ++j)
313 sum += obs[
static_cast<std::size_t
>(i * batch_size + j)];
314 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
317 for (
int i = 0; i < nb; ++i) grand += means[static_cast<std::size_t>(i)];
320 for (
int i = 0; i < nb; ++i) {
321 const double d = means[
static_cast<std::size_t
>(i)] - grand;
325 s.
stderr_ = std::sqrt((ss / (nb - 1)) / nb);
334 const std::size_t n = obs.size();
335 if (batch_size <= 0 || n <
static_cast<std::size_t
>(batch_size) * 2)
return s;
336 int step =
static_cast<int>(batch_size * (1.0 - overlap));
337 if (step < 1) step = 1;
338 const int nb =
static_cast<int>((n -
static_cast<std::size_t
>(batch_size)) /
339 static_cast<std::size_t
>(step)) + 1;
340 if (nb < 2)
return s;
341 std::vector<double> means(
static_cast<std::size_t
>(nb), 0.0);
342 for (
int i = 0; i < nb; ++i) {
343 const std::size_t start =
static_cast<std::size_t
>(i) *
static_cast<std::size_t
>(step);
345 for (
int j = 0; j < batch_size; ++j)
346 if (start +
static_cast<std::size_t
>(j) < n)
sum += obs[start +
static_cast<std::size_t
>(j)];
347 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
350 for (
int i = 0; i < nb; ++i) grand += means[static_cast<std::size_t>(i)];
353 for (
int i = 0; i < nb; ++i) {
354 const double d = means[
static_cast<std::size_t
>(i)] - grand;
359 s.
stderr_ = std::sqrt((adj * ss / (nb - 1)) / nb);
360 s.
df =
static_cast<int>((nb - 1) / adj);
361 if (s.
df < 1) s.
df = 1;
377 static const double t90[30] = {
378 6.314, 2.920, 2.353, 2.132, 2.015, 1.943, 1.895, 1.860, 1.833, 1.812,
379 1.796, 1.782, 1.771, 1.761, 1.753, 1.746, 1.740, 1.734, 1.729, 1.725,
380 1.721, 1.717, 1.714, 1.711, 1.708, 1.706, 1.703, 1.701, 1.699, 1.697};
381 static const double t95[30] = {
382 12.706, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228,
383 2.201, 2.179, 2.160, 2.145, 2.131, 2.120, 2.110, 2.101, 2.093, 2.086,
384 2.080, 2.074, 2.069, 2.064, 2.060, 2.056, 2.052, 2.048, 2.045, 2.042};
385 static const double t99[30] = {
386 63.657, 9.925, 5.841, 4.604, 4.032, 3.707, 3.499, 3.355, 3.250, 3.169,
387 3.106, 3.055, 3.012, 2.977, 2.947, 2.921, 2.898, 2.878, 2.861, 2.845,
388 2.831, 2.819, 2.807, 2.797, 2.787, 2.779, 2.771, 2.763, 2.756, 2.750};
389 const int idx = std::min(df, 30) - 1;
390 if (idx < 0)
return 1.96;
391 if (level >= 0.99)
return t99[idx];
392 if (level >= 0.95)
return t95[idx];
416 double low_freq_frac) {
417 const std::size_t n = obs.size();
418 const int m = (batch_size > 0) ?
static_cast<int>(n /
static_cast<std::size_t
>(batch_size)) : 0;
421 std::vector<double> means(
static_cast<std::size_t
>(m), 0.0);
422 for (
int i = 0; i < m; ++i) {
424 for (
int j = 0; j < batch_size; ++j)
425 sum += obs[
static_cast<std::size_t
>(i * batch_size + j)];
426 means[
static_cast<std::size_t
>(i)] =
sum / batch_size;
429 for (
int i = 0; i < m; ++i) grand += means[static_cast<std::size_t>(i)];
432 const int nf = m / 2;
434 std::vector<double> per(
static_cast<std::size_t
>(nf), 0.0);
435 for (
int k = 1; k <= nf; ++k) {
436 double re = 0.0, im = 0.0;
437 const double f = 2.0 * 3.14159265358979323846 * k / m;
438 for (
int t = 0; t < m; ++t) {
439 const double c = means[
static_cast<std::size_t
>(t)] - grand;
440 re += c * std::cos(f * t);
441 im += c * std::sin(f * t);
443 per[
static_cast<std::size_t
>(k - 1)] = (re * re + im * im) / m;
446 int npts =
static_cast<int>(nf * low_freq_frac);
447 if (npts < 3) npts = 3;
449 for (
int k = 0; k < npts; ++k)
450 if (!(per[
static_cast<std::size_t
>(k)] > 0.0))
return bm_statistics(obs, batch_size);
456 for (
int r = 0; r < 3; ++r)
457 for (
int c = 0; c < 4; ++c) A[r][c] = 0.0;
458 for (
int k = 0; k < npts; ++k) {
459 const double f = 2.0 * 3.14159265358979323846 * (k + 1) / m;
460 const double x[3] = {1.0, f, f * f};
461 const double y = std::log(per[
static_cast<std::size_t
>(k)]);
462 for (
int r = 0; r < 3; ++r) {
463 for (
int c = 0; c < 3; ++c) A[r][c] += x[r] * x[c];
467 for (
int i = 0; i < 3; ++i) {
469 for (
int r = i + 1; r < 3; ++r)
470 if (std::fabs(A[r][i]) > std::fabs(A[piv][i])) piv = r;
471 if (!(std::fabs(A[piv][i]) > 1e-300))
return bm_statistics(obs, batch_size);
473 for (
int c = 0; c < 4; ++c) std::swap(A[i][c], A[piv][c]);
474 for (
int r = 0; r < 3; ++r) {
475 if (r == i)
continue;
476 const double fct = A[r][i] / A[i][i];
477 for (
int c = i; c < 4; ++c) A[r][c] -= fct * A[i][c];
480 const double b0 = A[0][3] / A[0][0];
481 const double s0 = std::exp(b0);
482 if (!(s0 > 0.0) || !std::isfinite(s0))
return bm_statistics(obs, batch_size);
486 st.
stderr_ = std::sqrt(2.0 * 3.14159265358979323846 * s0 / m);
488 if (st.
df < 1) st.
df = 1;
518 void init(std::size_t M, std::size_t K,
const LdesOptions& o, std::uint64_t max_events) {
520 if (!enabled_)
return;
523 interval_ = (o.
cnvgchk > 0) ?
static_cast<std::uint64_t
>(o.
cnvgchk)
524 : std::max<std::uint64_t>(1, max_events / 50);
526 q_.assign(M, std::vector<std::vector<double>>(K));
527 u_.assign(M, std::vector<std::vector<double>>(K));
528 r_.assign(M, std::vector<std::vector<double>>(K));
529 t_.assign(M, std::vector<std::vector<double>>(K));
530 start_qt_.assign(M, std::vector<double>(K, 0.0));
531 start_bt_.assign(M, std::vector<double>(K, 0.0));
532 start_cmp_.assign(M, std::vector<double>(K, 0.0));
533 start_rsum_.assign(M, std::vector<double>(K, 0.0));
534 start_rcnt_.assign(M, std::vector<double>(K, 0.0));
540 std::uint64_t
interval()
const {
return interval_; }
544 if (!enabled_)
return;
545 const double dur = now - batch_start_;
547 reset_batch(acc, now);
550 for (std::size_t i = 0; i < nstations_; ++i)
551 for (std::size_t k = 0; k < nclasses_; ++k) {
552 const double ql = (acc.
tot_qlen[i][k] - start_qt_[i][k]) / dur;
553 q_[i][k].push_back(ql);
556 :
static_cast<double>(nservers[i] > 0 ? nservers[i] : 1);
557 u_[i][k].push_back((acc.
tot_busy[i][k] - start_bt_[i][k]) / (dur * c));
558 t_[i][k].push_back((acc.
completed[i][k] - start_cmp_[i][k]) / dur);
559 const double dn = acc.
resp_cnt[i][k] - start_rcnt_[i][k];
560 r_[i][k].push_back(dn > 0.0 ? (acc.
resp_sum[i][k] - start_rsum_[i][k]) / dn : 0.0);
562 reset_batch(acc, now);
566 bool converged(
const std::vector<std::vector<bool>>& off)
const {
567 if (!enabled_)
return false;
568 if (nstations_ == 0 || nclasses_ == 0)
return false;
569 if (
static_cast<int>(q_[0][0].size()) < min_batches_)
return false;
570 for (std::size_t i = 0; i < nstations_; ++i)
571 for (std::size_t k = 0; k < nclasses_; ++k) {
572 if (off[i][k])
continue;
573 if (!metric_ok(q_[i][k]))
return false;
574 if (!metric_ok(u_[i][k]))
return false;
575 if (!metric_ok(t_[i][k]))
return false;
576 bool any_positive =
false;
577 for (
double v : r_[i][k])
582 if (any_positive && !metric_ok(r_[i][k]))
return false;
587 int batches()
const {
return (nstations_ && nclasses_) ?
static_cast<int>(q_[0][0].size()) : 0; }
590 void reset_batch(
const Accum& acc,
double now) {
592 for (std::size_t i = 0; i < nstations_; ++i)
593 for (std::size_t k = 0; k < nclasses_; ++k) {
594 start_qt_[i][k] = acc.
tot_qlen[i][k];
595 start_bt_[i][k] = acc.
tot_busy[i][k];
597 start_rsum_[i][k] = acc.
resp_sum[i][k];
598 start_rcnt_[i][k] = acc.
resp_cnt[i][k];
602 bool metric_ok(
const std::vector<double>& b)
const {
603 const std::size_t n = b.size();
604 if (n < 2)
return false;
606 for (
double x : b)
sum += x;
607 const double mean =
sum / n;
609 if (std::fabs(mean) < 1e-12)
return true;
611 for (
double x : b) var += (x - mean) * (x - mean);
613 const double se = std::sqrt(var / n);
614 const double half =
t_critical(level_,
static_cast<int>(n) - 1) * se;
615 return half / std::fabs(mean) <= tol_;
618 bool enabled_ =
false;
619 double tol_ = 0.05, level_ = 0.95, batch_start_ = 0.0;
620 int min_batches_ = 20;
621 std::uint64_t interval_ = 1;
622 std::size_t nstations_ = 0, nclasses_ = 0;
623 std::vector<std::vector<std::vector<double>>> q_, u_, r_, t_;
624 std::vector<std::vector<double>> start_qt_, start_bt_, start_cmp_, start_rsum_, start_rcnt_;
654 const std::size_t t0 =
tr.applied ?
tr.index : 0;
656 for (std::size_t i = 0; i < M; ++i) {
657 for (std::size_t r = 0; r < K; ++r) {
658 if (t0 >= obs.
qlen[i][r].size())
continue;
659 const std::vector<double> q(obs.
qlen[i][r].begin() +
static_cast<std::ptrdiff_t
>(t0),
660 obs.
qlen[i][r].end());
661 if (q.size() >=
static_cast<std::size_t
>(o.
ciminobs)) {
662 const int b = std::max(o.
ciminbatch,
static_cast<int>(std::sqrt(
663 static_cast<double>(q.size()))));
668 std::vector<double> rates;
669 for (std::size_t k = t0 + 1; k < obs.
cmp[i][r].size(); ++k) {
670 const double dt = obs.
time[k] - obs.
time[k - 1];
671 if (dt > 0.0) rates.push_back((obs.
cmp[i][r][k] - obs.
cmp[i][r][k - 1]) / dt);
673 if (rates.size() >=
static_cast<std::size_t
>(o.
ciminobs)) {
674 const int b = std::max(o.
ciminbatch,
static_cast<int>(std::sqrt(
675 static_cast<double>(rates.size()))));
679 res.
TNCI(i, r) = half;
680 if (res.
TN.
rows() > i && res.
RN.
rows() > i && res.
UN(i, r) > 0.0 &&
682 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.
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.
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