LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sim_sts_quantile_areas.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_STS_QUANTILE_AREAS_H
6#define LINE_API_SIM_SIM_STS_QUANTILE_AREAS_H
7
8/**
9 * @file
10 * @ingroup api_sim
11 * Standardized time series areas of the batched quantile process.
12 *
13 * Port of matlab/src/api/sim/sim_sts_quantile_areas.m. The b*m observations are
14 * split into b nonoverlapping batches of size m; the function returns the
15 * signed standardized time series (STS) areas of the quantile-estimation
16 * process, the batched quantile estimators, and the three variance-parameter
17 * estimators the QUEST procedures build confidence intervals from.
18 *
19 * With yhat_p(j,m) the empirical p-quantile of batch j and yhat_p(j,k) that of
20 * its first k observations, the STS process of batch j is
21 * T_{j,m}(k/m) = (k/sqrt(m)) (yhat_p(j,m) - yhat_p(j,k)),
22 * its signed area is
23 * A_p(w;j,m) = m^{-1} sum_{k=1}^{m} w(k/m) T_{j,m}(k/m),
24 * and the three estimators of sigma_p^2 = lim n Var(ytilde_p(n)) are
25 * A_p(w;b,m) = b^{-1} sum_j A_p(w;j,m)^2 (STS area)
26 * N_p(b,m) = (b-1)^{-1} m sum_j (yhat_p(j,m)-ytilde_p(n))^2 (NBQ)
27 * V_p(w;b,m) = [b A_p(w;b,m) + (b-1) N_p(b,m)] / (2b-1) (combined)
28 * where ytilde_p(n) is the full-sample empirical p-quantile over all n = b*m
29 * observations. The first two have limiting chi-square laws on b and b-1
30 * degrees of freedom and are asymptotically independent, so the combined
31 * estimator carries 2b-1 degrees of freedom and is about sqrt(2) less variable
32 * than either component.
33 *
34 * WEIGHT FUNCTION. The requirement on w is that int_0^1 w(t)B(t)dt be standard
35 * normal for a standard Brownian bridge B; for a constant w = c that variance is
36 * c^2/12, so c = sqrt(12) is the normalizing choice and any other constant
37 * rescales every area and A_p by (c/sqrt(12))^2. Only constant weights are
38 * supported, as in the reference.
39 *
40 * COST. The prefix quantiles yhat_p(j,k) are exact order statistics, not a
41 * running approximation: a Fenwick tree over the within-batch ranks is advanced
42 * one observation at a time and searched by binary lifting, so the whole
43 * function is O(b m log m). MATLAB advances the b trees in lockstep to keep its
44 * k loop vectorized; here the batches are simply looped, which is the same
45 * arithmetic in a different order.
46 *
47 * A SINGLE BATCH carries no between-batch degrees of freedom, so N_p and V_p are
48 * NaN at b = 1 while areas and bqe stay valid; sim_firquest pools them across
49 * replications instead of reading them per replication.
50 *
51 * Reference: C. Alexopoulos, D. Goldsman, A. Lolos, K. D. Dingec, J. R. Wilson,
52 * "Steady-State Quantile Estimation Using Standardized Time Series", 2020/2023;
53 * A. Lolos et al., Proc. Winter Simulation Conference, 2023, theorems 1-3.
54 */
55
56#include <algorithm>
57#include <cmath>
58#include <cstddef>
59#include <numeric>
60#include <vector>
61
63#include "line/num/number.h"
64#include "line/util/error.h"
65
66namespace line {
67namespace sim {
68
69/** Batched-quantile statistics of one sample path. */
70template <class T>
72 std::vector<T> areas; ///< b signed STS areas A_p(w;j,m)
73 std::vector<T> bqe; ///< b batched quantile estimators yhat_p(j,m)
74 T quantile; ///< Full-sample empirical p-quantile ytilde_p(n)
75 T Ap; ///< Batched STS area estimator A_p(w;b,m)
76 T Np; ///< NBQ variance-parameter estimator N_p(b,m), NaN at b = 1
77 T Vp; ///< Combined variance-parameter estimator, NaN at b = 1
78 std::size_t b = 0; ///< Batch count
79 std::size_t m = 0; ///< Batch size
80 std::size_t n = 0; ///< Number of observations used, b*m
81};
82
83namespace detail {
84
85/** Fenwick prefix-count tree over the ranks 1..m of one batch. */
86class RankTree {
87public:
88 explicit RankTree(std::size_t m) : f_(m + 1, 0), m_(m) {
89 step_ = 1;
90 while (step_ * 2 <= m) step_ *= 2;
91 }
92
93 void add(std::size_t rank) {
94 for (std::size_t i = rank; i <= m_; i += i & (~i + 1)) ++f_[i];
95 }
96
97 /**
98 * Zero-based index of the L-th smallest rank inserted so far, i.e. MATLAB's
99 * pos + 1 with pos the last position whose prefix count stays below L.
100 */
101 std::size_t select(std::size_t L) const {
102 std::size_t pos = 0, rem = L;
103 for (std::size_t step = step_; step > 0; step >>= 1) {
104 const std::size_t cand = pos + step;
105 if (cand <= m_ && f_[cand] < rem) {
106 rem -= f_[cand];
107 pos = cand;
108 }
109 }
110 return pos;
111 }
112
113private:
114 std::vector<std::size_t> f_;
115 std::size_t m_;
116 std::size_t step_;
117};
118
119} // namespace detail
120
121/**
122 * @brief Standardized time series areas of the batched quantile process.
123 *
124 * @param Y exactly b*m finite observations, in sample-path order
125 * @param b batch count, positive
126 * @param m batch size, positive
127 * @param p quantile order in (0,1)
128 * @param weight constant STS weight function, sqrt(12) by default
129 */
130template <class T>
131StsQuantileStats<T> sim_sts_quantile_areas(const std::vector<T>& Y, std::size_t b, std::size_t m,
132 double p, double weight = std::sqrt(12.0)) {
134 "sim_sts_quantile_areas: the areas carry the irrational normalizing weight "
135 "sqrt(12)/(m sqrt(m)), so exact arithmetic is refused");
136 if (b < 1) throw InputError("sim_sts_quantile_areas: the batch count b must be positive");
137 if (m < 1) throw InputError("sim_sts_quantile_areas: the batch size m must be positive");
138 if (!(p > 0.0) || !(p < 1.0))
139 throw InputError("sim_sts_quantile_areas: p must be a real scalar in (0,1)");
140 if (weight == 0.0)
141 throw InputError("sim_sts_quantile_areas: weight must be a nonzero real scalar");
142
143 const std::size_t n = b * m;
144 if (Y.size() != n)
145 throw InputError("sim_sts_quantile_areas: Y must hold exactly b*m observations");
146 for (std::size_t i = 0; i < n; ++i)
147 if (!detail::num_isfinite(Y[i]))
148 throw InputError("sim_sts_quantile_areas: the sample path must be finite");
149
151 st.b = b;
152 st.m = m;
153 st.n = n;
154 st.areas.assign(b, num_traits<T>::from_int(0));
155 st.bqe.assign(b, num_traits<T>::from_int(0));
156
157 const T wgt = num_traits<T>::from_double(weight);
158 const T md = num_traits<T>::from_int(static_cast<long>(m));
159 const T scale = T(wgt / T(md * detail::num_sqrt(md)));
160 const std::size_t bqeIdx = static_cast<std::size_t>(
161 std::ceil(static_cast<double>(m) * p)) - 1;
162
163 std::vector<std::size_t> ord(m), rnk(m);
164 for (std::size_t j = 0; j < b; ++j) {
165 const T* col = &Y[j * m];
166
167 std::iota(ord.begin(), ord.end(), static_cast<std::size_t>(0));
168 // stable, so ties keep sample-path order exactly as MATLAB's sort does
169 std::stable_sort(ord.begin(), ord.end(),
170 [col](std::size_t a, std::size_t c) { return col[a] < col[c]; });
171 std::vector<T> sorted(m);
172 for (std::size_t r = 0; r < m; ++r) {
173 sorted[r] = col[ord[r]];
174 rnk[ord[r]] = r + 1;
175 }
176
177 st.bqe[j] = sorted[bqeIdx];
178
179 detail::RankTree tree(m);
180 T acc = num_traits<T>::from_int(0);
181 for (std::size_t k = 1; k <= m; ++k) {
182 tree.add(rnk[k - 1]);
183 const std::size_t L =
184 static_cast<std::size_t>(std::ceil(p * static_cast<double>(k)));
185 const T qk = sorted[tree.select(L)];
186 acc += T(num_traits<T>::from_int(static_cast<long>(k)) * T(st.bqe[j] - qk));
187 }
188 st.areas[j] = T(scale * acc);
189 }
190
191 std::vector<T> all(Y);
192 std::sort(all.begin(), all.end());
193 st.quantile = all[static_cast<std::size_t>(std::ceil(static_cast<double>(n) * p)) - 1];
194
195 T sumSq = num_traits<T>::from_int(0);
196 for (std::size_t j = 0; j < b; ++j) sumSq += T(st.areas[j] * st.areas[j]);
197 st.Ap = T(sumSq / num_traits<T>::from_int(static_cast<long>(b)));
198
199 if (b >= 2) {
200 T sd = num_traits<T>::from_int(0);
201 for (std::size_t j = 0; j < b; ++j) {
202 const T d = T(st.bqe[j] - st.quantile);
203 sd += T(d * d);
204 }
205 st.Np = T(md * sd / num_traits<T>::from_int(static_cast<long>(b - 1)));
206 st.Vp = T((num_traits<T>::from_int(static_cast<long>(b)) * st.Ap +
207 num_traits<T>::from_int(static_cast<long>(b - 1)) * st.Np) /
208 num_traits<T>::from_int(static_cast<long>(2 * b - 1)));
209 } else {
210 st.Np = detail::num_nan<T>();
211 st.Vp = detail::num_nan<T>();
212 }
213 return st;
214}
215
216} // namespace sim
217} // namespace line
218
219#endif // LINE_API_SIM_SIM_STS_QUANTILE_AREAS_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
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.
Number-type abstraction for the templated API port.
Shared arithmetic helpers for the templated simulation output-analysis port.
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).