LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sim_quest_heuristic_ci.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_QUEST_HEURISTIC_CI_H
6#define LINE_API_SIM_SIM_QUEST_HEURISTIC_CI_H
7
8/**
9 * @file
10 * @ingroup api_sim
11 * Fallback interval used when a QUEST stage test fails.
12 *
13 * Port of matlab/src/api/sim/sim_quest_heuristic_ci.m. Three intervals are formed
14 * and the smallest interval containing all of them is returned, which is the
15 * article's prescription:
16 *
17 * Two symmetric intervals of half-width
18 * h = max(t_{1-alpha/2,K} sqrt(Ap/nstar), t_{1-alpha/2,K-1} sqrt(Np/nstar)),
19 * one about the full-sample quantile and one about the average of the batched
20 * quantile estimators. Taking the WIDER of the two variance components is
21 * deliberately conservative: a stage test has just failed, so neither
22 * component can be trusted.
23 *
24 * Willink's asymmetric interval, which corrects the batched quantile
25 * estimators for skewness through the cube-root transform
26 * G(zeta) = ([1+6 gamma(zeta-gamma)]^(1/3)-1)/(2 gamma) with
27 * gamma = skewness/(6 sqrt(K)), evaluated at both t-quantiles so the two arms
28 * differ.
29 *
30 * useAutocorr additionally scales the asymmetric arms by
31 * max(sqrt((1+phi1)/(1-phi1)), 1), with phi1 the lag-1 autocorrelation of the
32 * batch quantiles. It is true for sim_fquest, where they come from one sample
33 * path and can stay correlated, and false for sim_firquest, where they come
34 * from independent replications and the article drops the correction.
35 *
36 * Np ARRIVES AS NaN when the caller had a single batch per path. The reference
37 * relies on MATLAB's max() omitting NaN, so the interval then rests on the Ap
38 * component alone; the max here omits NaN explicitly for the same reason, since
39 * a C++ comparison against NaN is false in both directions and the naive form
40 * would propagate the NaN into the delivered endpoints.
41 *
42 * Reference: R. Willink, "A Confidence Interval and Test for the Mean of an
43 * Asymmetric Distribution", Commun. Statist. Theory Methods 34, 2005;
44 * A. Lolos et al., Proc. Winter Simulation Conference, 2023, step 10, and
45 * Proc. Winter Simulation Conference, 2025, equations 8 to 10.
46 */
47
48#include <algorithm>
49#include <cmath>
50#include <cstddef>
51#include <vector>
52
55#include "line/num/number.h"
56#include "line/util/error.h"
57
58namespace line {
59namespace sim {
60
61/** A confidence interval, asymmetric about the point estimate in general. */
62template <class T>
66};
67
68namespace detail {
69
70/** MATLAB max() of two scalars, i.e. the larger one with NaN omitted. */
71template <class T>
72inline T max_omitnan(const T& a, const T& b) {
73 if (num_isnan(a)) return b;
74 if (num_isnan(b)) return a;
75 return a < b ? b : a;
76}
77
78/** Willink's skewness-adjustment transform, the identity for tiny skewness. */
79template <class T>
80inline T willink_g(const T& zeta, const T& gamma) {
81 const T lim = num_traits<T>::from_double(0.001);
82 if (num_abs(gamma) <= lim) return zeta;
83 const T zero = num_traits<T>::from_int(0);
84 const T one = num_traits<T>::from_int(1);
85 const T six = num_traits<T>::from_int(6);
86 const T third = num_traits<T>::from_rational(1, 3);
87 const T arg = T(one + six * gamma * T(zeta - gamma));
88 // the cube root is taken on the reals, the argument may turn negative for a
89 // strongly skewed and small batch sample
90 const T root = arg >= zero ? num_pow(arg, third) : T(-num_pow(T(-arg), third));
91 return T(T(root - one) / T(num_traits<T>::from_int(2) * gamma));
92}
93
94} // namespace detail
95
96/**
97 * @brief Fallback interval used when a QUEST stage test fails.
98 *
99 * @param bqe the K batched quantile estimators, pooled over replications
100 * for sim_firquest
101 * @param centre the full-sample empirical quantile
102 * @param Ap STS area variance-parameter estimator
103 * @param Np NBQ variance-parameter estimator, may be NaN
104 * @param nstar number of observations Ap and Np were computed from
105 * @param alpha nominal non-coverage
106 * @param useAutocorr apply the lag-1 correction to the asymmetric arms
107 */
108template <class T>
109QuestInterval<T> sim_quest_heuristic_ci(const std::vector<T>& bqe, const T& centre, const T& Ap,
110 const T& Np, std::size_t nstar, double alpha,
111 bool useAutocorr) {
113 "sim_quest_heuristic_ci: t quantiles and square roots, so exact arithmetic is "
114 "refused");
115 const std::size_t K = bqe.size();
116 if (K < 3)
117 throw InputError("sim_quest_heuristic_ci: the heuristic interval needs at least 3 batch "
118 "quantiles");
119 if (nstar < 1)
120 throw InputError("sim_quest_heuristic_ci: nstar must be positive");
121 if (!(alpha > 0.0) || !(alpha < 1.0))
122 throw InputError("sim_quest_heuristic_ci: alpha must be a real scalar in (0,1)");
123
124 const double Kd = static_cast<double>(K);
125 const T nst = num_traits<T>::from_int(static_cast<long>(nstar));
126 const T tK = num_traits<T>::from_double(sim_tinv(1.0 - alpha / 2.0, Kd));
127 const T tKm1 = num_traits<T>::from_double(sim_tinv(1.0 - alpha / 2.0, Kd - 1.0));
128
129 const T half = detail::max_omitnan(T(tK * detail::num_sqrt(T(Ap / nst))),
130 T(tKm1 * detail::num_sqrt(T(Np / nst))));
131
133 for (std::size_t i = 0; i < K; ++i) sum += bqe[i];
134 const T bqeBar = T(sum / num_traits<T>::from_int(static_cast<long>(K)));
135
136 const T Km1 = num_traits<T>::from_int(static_cast<long>(K - 1));
138 for (std::size_t i = 0; i < K; ++i) {
139 const T d = T(bqe[i] - bqeBar);
140 const T dt = T(bqe[i] - centre);
141 s2 += T(d * d);
142 s2t += T(dt * dt);
143 }
144 const T S2 = T(s2 / Km1);
145 const T S2tilde = T(s2t / Km1);
146
148 ci.lower = std::min(T(centre - half), T(bqeBar - half));
149 ci.upper = std::max(T(centre + half), T(bqeBar + half));
150 if (!(S2 > num_traits<T>::from_int(0))) return ci;
151
152 const T sdev = detail::num_sqrt(S2);
153 T cube = num_traits<T>::from_int(0);
154 for (std::size_t i = 0; i < K; ++i) {
155 const T z = T(T(bqe[i] - bqeBar) / sdev);
156 cube += T(z * z * z);
157 }
158 const T skew = T(num_traits<T>::from_double(Kd / ((Kd - 1.0) * (Kd - 2.0))) * cube);
159 const T gamma = T(skew / num_traits<T>::from_double(6.0 * std::sqrt(Kd)));
160
161 T varphi = num_traits<T>::from_int(1);
162 if (useAutocorr) {
163 T lag = num_traits<T>::from_int(0);
164 for (std::size_t i = 0; i + 1 < K; ++i)
165 lag += T(T(bqe[i] - bqeBar) * T(bqe[i + 1] - bqeBar));
166 const T phi1 = T(lag / T(Km1 * S2));
167 const T one = num_traits<T>::from_int(1);
168 if (num_abs(phi1) < one) {
169 const T v = detail::num_sqrt(T(T(one + phi1) / T(one - phi1)));
170 varphi = v < one ? one : v;
171 }
172 }
173
174 const T tq = num_traits<T>::from_double(sim_tinv(1.0 - alpha / 2.0, Kd - 1.0));
175 const T scale = T(varphi * detail::num_sqrt(T(S2tilde / num_traits<T>::from_int(
176 static_cast<long>(K)))));
177 const T G1 = T(detail::willink_g(tq, gamma) * scale);
178 const T G2 = T(detail::willink_g(T(-tq), gamma) * scale);
179
180 const T armA = T(centre - G1);
181 const T armB = T(centre - G2);
182 ci.lower = std::min(ci.lower, std::min(armA, armB));
183 ci.upper = std::max(ci.upper, std::max(armA, armB));
184 return ci;
185}
186
187} // namespace sim
188} // namespace line
189
190#endif // LINE_API_SIM_SIM_QUEST_HEURISTIC_CI_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
double sim_tinv(double p, double nu)
Quantile function of Student's t distribution.
Definition sim_dist.h:77
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.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Normal and Student t quantiles used by the output-analysis routines.
Shared arithmetic helpers for the templated simulation output-analysis port.
A confidence interval, asymmetric about the point estimate in general.