LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sim_fquest.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_SIM_SIM_FQUEST_H
6#define LINE_API_SIM_SIM_FQUEST_H
7
8/**
9 * @file
10 * @ingroup api_sim
11 * Fixed-sample-size confidence interval for a steady-state quantile.
12 *
13 * Port of matlab/src/api/sim/sim_fquest.m. The sample path Y has arbitrary fixed
14 * length; no sequential control of the run length is needed. The procedure is
15 * FQUEST and has four blocks:
16 *
17 * Warmup. Starting from b = b0 and m = m0 it computes the b signed STS areas
18 * of the batched quantile process and tests them for randomness with von
19 * Neumann's ratio at the DECAYING significance beta*exp(-eta*(l-1)^theta) on
20 * iteration l, growing m by sqrt(2) whenever the test rejects. The decay is
21 * what terminates the loop once m can no longer grow: with a fixed
22 * significance the last iteration would repeat forever at m = floor(N/b).
23 *
24 * Truncation. The first batch is deleted, which is the entire warmup
25 * treatment; there is no separate transient detector.
26 *
27 * Batch-count selection. With b stepping down through s and m = floor(N* / b),
28 * four tests must pass in order: von Neumann and Shapiro-Wilk on the signed
29 * areas, then von Neumann and Shapiro-Wilk on the batched quantile
30 * estimators. b only ever decreases, and a failure at the last entry of s
31 * ends the stage.
32 *
33 * Delivery. When all four tests pass the interval is
34 * ytilde_p(n*) +- t_{1-alpha/2, 2b-1} sqrt(V_p(w;b,m)/n*).
35 * Otherwise the sample was too small, `heuristic` is true, and under
36 * options.force the interval returned is the union of the wider of the two
37 * single-component intervals and Willink's skewness- and correlation-adjusted
38 * asymmetric interval.
39 *
40 * WHAT THIS MAY BE RUN ON. Applicability is a condition on the output process,
41 * not on the model that produced it: geometric moment contraction (Wu 2005), a
42 * density positive and differentiable at the quantile of interest, short-range
43 * dependence and an FCLT for the indicator process. Two exclusions follow and
44 * neither raises an error, so they have to be observed by the caller. Do NOT use
45 * this on integer-valued output such as a queue length: the marginal has no
46 * density and the batched quantile has no Bahadur representation. And
47 * heavy-tailed service, which can break geometric moment contraction, is outside
48 * the theory. Use it on continuous output, that is response, waiting and
49 * sojourn times.
50 *
51 * A DELIVERED INTERVAL MAY WELL BE THE HEURISTIC ONE: 22% to 65% of runs took
52 * that fallback in the reference's own test bed, rising with p. Read
53 * `heuristic` before quoting the half-width as an asymptotically justified one.
54 * Coverage measured there on M/M/1 waiting times at N = 200000 over 500
55 * replications was 95.2% at p = 0.5, 96.2% at p = 0.9 and 95.6% at p = 0.99
56 * against a nominal 95%, with the delivered half-width exceeding the empirically
57 * needed one by 1.16, 1.24 and 1.99 respectively.
58 *
59 * Reference: A. Lolos, C. Alexopoulos, D. Goldsman, K. D. Dingec, A. C. Mokashi,
60 * J. R. Wilson, "A Fixed-Sample-Size Method for Estimating Steady-State
61 * Quantiles", Proc. Winter Simulation Conference, 2023.
62 */
63
64#include <cmath>
65#include <cstddef>
66#include <string>
67#include <vector>
68
76#include "line/num/number.h"
77#include "line/util/error.h"
78
79namespace line {
80namespace sim {
81
82/** Point estimate and interval delivered by the QUEST procedures. */
83template <class T>
85 T estimate; ///< Full-sample empirical p-quantile of the truncated path
86 T lower; ///< Lower confidence limit, NaN if refused
87 T upper; ///< Upper confidence limit, NaN if refused
88 T halfwidth; ///< (upper-lower)/2, attained only on average when asymmetric
89 std::size_t b = 0; ///< Final batch count, per replication in sim_firquest
90 std::size_t m = 0; ///< Final batch size
91 std::size_t n = 0; ///< Observations the interval rests on
92 std::size_t R = 1; ///< Replications, 1 for sim_fquest
93 std::size_t truncated = 0; ///< Observations deleted from the front of each path
94 T Ap; ///< STS area variance-parameter estimator
95 T Np; ///< NBQ variance-parameter estimator
96 T Vp; ///< Combined variance-parameter estimator
97 bool heuristic = false; ///< True when a stage test failed
98 std::vector<std::string> warnings; ///< Diagnostics, empty on a clean run
99};
100
101namespace detail {
102
103/**
104 * One warmup iteration's batch size update, shared by the two QUEST procedures.
105 * Returns the next batch size and sets atMax when the sample path can no longer
106 * support a larger one.
107 */
108inline long warmup_next_m(long N, long b, long m, bool& atMax) {
109 const long full = N / b;
110 const long next = static_cast<long>(std::llround(static_cast<double>(m) * std::sqrt(2.0)));
111 if (N < b * next && next != full) return full;
112 if (N < b * next) {
113 atMax = true;
114 return full;
115 }
116 return next;
117}
118
119} // namespace detail
120
121/**
122 * @brief Fixed-sample-size confidence interval for a steady-state quantile.
123 *
124 * @param Y one simulation sample path, finite
125 * @param p quantile order in (0,1)
126 * @param alpha nominal non-coverage, 0.05 by default
127 * @param options procedure constants, see sim_quest_options
128 */
129template <class T>
130QuestResult<T> sim_fquest(const std::vector<T>& Y, double p, double alpha = 0.05,
131 const QuestOptions& options = QuestOptions()) {
133 "sim_fquest: the interval is a t quantile times a square root, so exact "
134 "arithmetic is refused");
135 const QuestOptions opt = sim_quest_options(options);
136
137 if (!(p > 0.0) || !(p < 1.0))
138 throw InputError("sim_fquest: p must be a real scalar in (0,1)");
139 if (!(alpha > 0.0) || !(alpha < 1.0))
140 throw InputError("sim_fquest: alpha must be a real scalar in (0,1)");
141 const long sLast = opt.s.back();
142 if (sLast < 3)
143 throw InputError("sim_fquest: the stage tests need at least 3 batches, so min(s) >= 3");
144
145 const long N = static_cast<long>(Y.size());
146 for (std::size_t i = 0; i < Y.size(); ++i)
147 if (!detail::num_isfinite(Y[i]))
148 throw InputError("sim_fquest: the sample path must be finite");
149 if (N < 2 * sLast)
150 throw InputError("sim_fquest: the sample path is too short for the stage tests");
151
152 QuestResult<T> res;
153
154 // ---- warmup: grow the batch size until the signed areas look random
155 long b = opt.b0;
156 long m = opt.m0;
157 if (N < b * m) m = N / b;
158 if (m < 1)
159 throw InputError("sim_fquest: the sample path is too short for the initial batch count b0");
160
161 long ell = 1;
162 bool atMax = false, passed = false;
163 while (true) {
164 const std::vector<T> head(Y.begin(), Y.begin() + static_cast<std::ptrdiff_t>(b * m));
166 head, static_cast<std::size_t>(b), static_cast<std::size_t>(m), p, opt.weight);
167 const double sig =
168 opt.beta * std::exp(-opt.eta * std::pow(static_cast<double>(ell - 1), opt.theta));
169 if (!sim_vonneumann<T>(st.areas, sig).reject) {
170 passed = true;
171 break;
172 }
173 if (atMax) break;
174 ++ell;
175 m = detail::warmup_next_m(N, b, m, atMax);
176 if (m < 1) break;
177 }
178 if (!passed)
179 res.warnings.push_back("the warmup randomness test could not be passed at the largest "
180 "admissible batch size, the sample path is too short");
181
182 // ---- truncation: delete the first batch
183 const long truncated = m > 0 ? m : 0;
184 const std::vector<T> Yt(Y.begin() + static_cast<std::ptrdiff_t>(truncated), Y.end());
185 const long Nstar = static_cast<long>(Yt.size());
186
187 // ---- batch-count selection: four tests in order, b only decreases
188 std::size_t v = 0;
189 b = opt.s[v];
190 m = Nstar / b;
191 bool ok = true, haveStats = false;
193 for (int stage = 1; stage <= 4; ++stage) {
194 while (true) {
195 if (m < 1) {
196 ok = false;
197 break;
198 }
199 const std::vector<T> kept(Yt.end() - static_cast<std::ptrdiff_t>(b * m), Yt.end());
200 stats = sim_sts_quantile_areas<T>(kept, static_cast<std::size_t>(b),
201 static_cast<std::size_t>(m), p, opt.weight);
202 haveStats = true;
203 const std::vector<T>& sample = stage <= 2 ? stats.areas : stats.bqe;
204 const bool reject = (stage % 2 == 1) ? sim_vonneumann<T>(sample, opt.beta).reject
205 : sim_shapirowilk<T>(sample, opt.beta).reject;
206 if (!reject) break;
207 ++v;
208 if (v >= opt.s.size()) {
209 ok = false;
210 break;
211 }
212 b = opt.s[v];
213 m = Nstar / b;
214 }
215 if (!ok) break;
216 }
217
218 if (!haveStats || m < 1)
219 throw InputError("sim_fquest: the sample path is too short to form min(s) batches");
220
221 res.b = static_cast<std::size_t>(b);
222 res.m = static_cast<std::size_t>(m);
223 res.n = stats.n;
224 res.truncated = static_cast<std::size_t>(truncated);
225 res.estimate = stats.quantile;
226 res.Ap = stats.Ap;
227 res.Np = stats.Np;
228 res.Vp = stats.Vp;
229
230 const T nst = num_traits<T>::from_int(static_cast<long>(stats.n));
231 if (ok) {
232 const T t = num_traits<T>::from_double(
233 sim_tinv(1.0 - alpha / 2.0, static_cast<double>(2 * b - 1)));
234 const T half = T(t * detail::num_sqrt(T(stats.Vp / nst)));
235 res.lower = T(res.estimate - half);
236 res.upper = T(res.estimate + half);
237 res.halfwidth = half;
238 res.heuristic = false;
239 } else {
240 res.warnings.push_back("a randomness or normality test failed at b = " +
241 std::to_string(opt.s.back()) +
242 ", the delivered interval is heuristic");
243 res.heuristic = true;
244 if (opt.force) {
246 stats.bqe, res.estimate, stats.Ap, stats.Np, stats.n, alpha, true);
247 res.lower = ci.lower;
248 res.upper = ci.upper;
249 res.halfwidth = T(T(ci.upper - ci.lower) / num_traits<T>::from_int(2));
250 } else {
251 res.lower = detail::num_nan<T>();
252 res.upper = detail::num_nan<T>();
253 res.halfwidth = detail::num_nan<T>();
254 }
255 }
256 return res;
257}
258
259} // namespace sim
260} // namespace line
261
262#endif // LINE_API_SIM_SIM_FQUEST_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
QuestResult< T > sim_fquest(const std::vector< T > &Y, double p, double alpha=0.05, const QuestOptions &options=QuestOptions())
Fixed-sample-size confidence interval for a steady-state quantile.
Definition sim_fquest.h:130
StsQuantileStats< T > sim_sts_quantile_areas(const std::vector< T > &Y, std::size_t b, std::size_t m, double p, double weight=std::sqrt(12.0))
Standardized time series areas of the batched quantile process.
VonNeumannResult< T > sim_vonneumann(const std::vector< T > &x, double alpha=0.05)
Von Neumann ratio test for randomness of a sequence.
double sim_tinv(double p, double nu)
Quantile function of Student's t distribution.
Definition sim_dist.h:77
QuestOptions sim_quest_options(const QuestOptions &options=QuestOptions())
Validates an option set and returns it.
ShapiroWilkResult< T > sim_shapirowilk(const std::vector< T > &x, double alpha=0.05)
Shapiro-Wilk test for univariate normality.
QuestInterval< T > sim_quest_heuristic_ci(const std::vector< T > &bqe, const T &centre, const T &Ap, const T &Np, std::size_t nstar, double alpha, bool useAutocorr)
Fallback interval used when a QUEST stage test fails.
Number-type abstraction for the templated API port.
Normal and Student t quantiles used by the output-analysis routines.
Fallback interval used when a QUEST stage test fails.
Options of the QUEST procedures, with the published FQUEST defaults.
Shapiro-Wilk test for univariate normality.
Standardized time series areas of the batched quantile process.
Shared arithmetic helpers for the templated simulation output-analysis port.
Von Neumann ratio test for randomness of a sequence.
A confidence interval, asymmetric about the point estimate in general.
Procedure constants shared by sim_fquest and sim_firquest.
Point estimate and interval delivered by the QUEST procedures.
Definition sim_fquest.h:84
T halfwidth
(upper-lower)/2, attained only on average when asymmetric
Definition sim_fquest.h:88
T estimate
Full-sample empirical p-quantile of the truncated path.
Definition sim_fquest.h:85
std::size_t truncated
Observations deleted from the front of each path.
Definition sim_fquest.h:93
T upper
Upper confidence limit, NaN if refused.
Definition sim_fquest.h:87
T Vp
Combined variance-parameter estimator.
Definition sim_fquest.h:96
T Ap
STS area variance-parameter estimator.
Definition sim_fquest.h:94
std::size_t b
Final batch count, per replication in sim_firquest.
Definition sim_fquest.h:89
std::size_t R
Replications, 1 for sim_fquest.
Definition sim_fquest.h:92
std::size_t n
Observations the interval rests on.
Definition sim_fquest.h:91
bool heuristic
True when a stage test failed.
Definition sim_fquest.h:97
T lower
Lower confidence limit, NaN if refused.
Definition sim_fquest.h:86
std::size_t m
Final batch size.
Definition sim_fquest.h:90
std::vector< std::string > warnings
Diagnostics, empty on a clean run.
Definition sim_fquest.h:98
T Np
NBQ variance-parameter estimator.
Definition sim_fquest.h:95
Batched-quantile statistics of one sample path.
std::size_t n
Number of observations used, b*m.
T Ap
Batched STS area estimator A_p(w;b,m).
std::vector< T > areas
b signed STS areas A_p(w;j,m)
T Np
NBQ variance-parameter estimator N_p(b,m), NaN at b = 1.
std::vector< T > bqe
b batched quantile estimators yhat_p(j,m)
T Vp
Combined variance-parameter estimator, NaN at b = 1.
T quantile
Full-sample empirical p-quantile ytilde_p(n).