LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_ordstat_exp.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_FJ_ORDSTAT_EXP_H
6#define LINE_API_FJ_ORDSTAT_EXP_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Mean of the k-th smallest of n independent EXPONENTIAL branch completion
12 * times, i.e. the instant a k-of-n (quorum) join fires. k = n is the ordinary
13 * AND-join, the maximum, and k = 1 the minimum.
14 *
15 * Templated port of matlab/src/api/fj/fj_ordstat_exp.m, cross-checked against
16 * jar/src/main/java/jline/api/fj/FJ_ordstat_exp.java.
17 *
18 * With lambda_i = 1/ri(i) and m = n-k stragglers allowed,
19 *
20 * E[X_(k)] = sum_{j=m+1..n} (-1)^(j-m-1) C(j-1,m) e_j,
21 * e_j = sum_{|S|=j} 1 / sum_{i in S} lambda_i
22 *
23 * the inclusion-exclusion identity for the order statistics of independent
24 * exponentials. At m = 0 it collapses to sum_j (-1)^(j-1) e_j, the classical
25 * expression for the maximum, TERM BY TERM: a full join therefore evaluates
26 * exactly as it did before this header existed, which is what keeps the MMT
27 * fork-join fixed point bit-identical on a standard join.
28 *
29 * The exact path is RATIONAL -- reciprocals, sums and integer binomials only --
30 * so it holds in an exact field and is not gated on has_transcendental. Only
31 * the large-n fallback is, since fj_quorum_moments fits a standard deviation.
32 *
33 * The sum has 2^n terms and its signs alternate, so it is evaluated exactly
34 * only while the branch count is small. Beyond FJ_ORDSTAT_MAX_EXACT branches a
35 * genuine quorum (k < n) is evaluated by fj_quorum_moments instead, whose
36 * Poisson-binomial recurrence adds no cancellation; a full join keeps the exact
37 * path at every n so that no existing result moves.
38 *
39 * Reference: A. Thomasian, "Analysis of Fork/Join and Related Queueing
40 * Systems", ACM Computing Surveys 47(2), Article 17, 2014, Sec. 3 (Eq. 18-19).
41 */
42
43#include <cmath>
44#include <cstddef>
45#include <vector>
46
49#include "line/num/number.h"
50#include "line/util/error.h"
51
52namespace line {
53namespace fj {
54
55/** Branch count above which a genuine quorum leaves the exact alternating sum. */
56static const std::size_t FJ_ORDSTAT_MAX_EXACT = 15;
57
58namespace detail {
59
60/**
61 * The large-n quorum fallback, compiled only where the arithmetic supports it.
62 * An exact field has no sqrt, so there it refuses rather than silently
63 * degrading to the alternating sum it was reached to avoid.
64 */
65template <class T, bool HasTranscendental = num_traits<T>::has_transcendental>
66struct OrdstatExpFallback {
67 static T eval(const std::vector<T>& ri, std::size_t k) {
68 // Branch times are taken as exponential, so the variance is the square
69 // of the mean.
70 std::vector<T> var(ri.size());
71 for (std::size_t i = 0; i < ri.size(); ++i) var[i] = ri[i] * ri[i];
72 return fj_quorum_moments(ri, var, k).m;
73 }
74};
75
76template <class T>
77struct OrdstatExpFallback<T, false> {
78 static T eval(const std::vector<T>&, std::size_t) {
79 throw InputError(
80 "fj_ordstat_exp: a quorum over more than FJ_ORDSTAT_MAX_EXACT branches needs the "
81 "two-moment fallback, which requires transcendental arithmetic");
82 }
83};
84
85} // namespace detail
86
87/**
88 * @brief Mean of the k-th smallest of n independent EXPONENTIAL branch
89 * completion times, i.e. the instant a k-of-n (quorum) join fires.
90 *
91 * @param ri branch completion time means
92 * @param k quorum, 1 <= k <= ri.size()
93 * @return the mean instant the k-of-n join fires
94 */
95template <class T>
96T fj_ordstat_exp(const std::vector<T>& ri, std::size_t k) {
97 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
98 std::vector<T> r;
99 r.reserve(ri.size());
100 for (std::size_t i = 0; i < ri.size(); ++i)
101 // an exact field carries no NaN or infinity, so the test goes through
102 // the double image, which is where a branch time can be either
103 if (std::isfinite(num_traits<T>::to_double(ri[i]))) r.push_back(ri[i]);
104 std::size_t n = r.size();
105 if (n == 0) return zero;
106 if (k < 1 || k > n) throw InputError("fj_ordstat_exp: k must satisfy 1 <= k <= n");
107 // A branch of zero mean completes instantly: it never delays the join and it
108 // counts toward the quorum at once. Removing it here keeps the reciprocal
109 // below finite, which the exact field requires and IEEE only tolerates.
110 std::size_t nzero = 0;
111 for (std::size_t i = 0; i < n; ++i)
112 if (!(r[i] > zero)) ++nzero;
113 if (nzero > 0) {
114 if (k <= nzero) return zero;
115 k -= nzero;
116 std::vector<T> pos;
117 pos.reserve(n - nzero);
118 for (std::size_t i = 0; i < n; ++i)
119 if (r[i] > zero) pos.push_back(r[i]);
120 r.swap(pos);
121 n = r.size();
122 }
123 if (n == 1) return r[0];
124
125 if (k < n && n > FJ_ORDSTAT_MAX_EXACT) return detail::OrdstatExpFallback<T>::eval(r, k);
126
127 std::vector<T> lambdai(n);
128 for (std::size_t i = 0; i < n; ++i) lambdai[i] = one / r[i];
129
130 const std::size_t nstrag = n - k;
131 T total = zero;
132 // Walk the subsets of each size j by an index vector, so no 2^n table is
133 // materialised and the exact field never leaves the loop.
134 for (std::size_t j = nstrag + 1; j <= n; ++j) {
135 T ej = zero;
136 std::vector<std::size_t> idx(j);
137 for (std::size_t i = 0; i < j; ++i) idx[i] = i;
138 while (true) {
139 T s = zero;
140 for (std::size_t i = 0; i < j; ++i) s = s + lambdai[idx[i]];
141 ej = ej + one / s;
142 // next combination in lexicographic order
143 std::size_t p = j;
144 while (p > 0 && idx[p - 1] == n - j + (p - 1)) --p;
145 if (p == 0) break;
146 ++idx[p - 1];
147 for (std::size_t i = p; i < j; ++i) idx[i] = idx[i - 1] + 1;
148 }
149 // C(j-1, nstrag) is an integer and stays one in the field
150 long long binom = 1;
151 for (std::size_t i = 0; i < nstrag; ++i)
152 binom = binom * static_cast<long long>(j - 1 - i) / static_cast<long long>(i + 1);
153 const T term = num_traits<T>::from_int(static_cast<int>(binom)) * ej;
154 if (((j - nstrag - 1) % 2) == 0)
155 total = total + term;
156 else
157 total = total - term;
158 }
159 return total;
160}
161
162} // namespace fj
163} // namespace line
164
165#endif // LINE_API_FJ_ORDSTAT_EXP_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Mean and variance of a k-of-n (quorum) join completion time, from the mean and variance of each branc...
Shared return types and arithmetic helpers for the templated fork-join port.
T fj_ordstat_exp(const std::vector< T > &ri, std::size_t k)
Mean of the k-th smallest of n independent EXPONENTIAL branch completion times, i....
static const std::size_t FJ_ORDSTAT_MAX_EXACT
Branch count above which a genuine quorum leaves the exact alternating sum.
FJQuorumMomentsResult< T > fj_quorum_moments(const std::vector< T > &branchMeans, const std::vector< T > &branchVars, std::size_t k)
Mean and variance of a k-of-n (quorum) join completion time, from the mean and variance of each branc...
Number-type abstraction for the templated API port.