LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_stdf_heur.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_PFQN_STDF_HEUR_H
6#define LINE_API_PFQN_STDF_HEUR_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Heuristic sojourn-time distribution at multiserver FCFS stations, a variant
12 * of J. McKenna, JACM 1987.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_stdf_heur.m. See pfqn_stdf.h for
15 * the method; this file documents only where the heuristic departs from it.
16 *
17 * 1. The level CDF is built PER CLASS. Below the server count it uses that
18 * class's own rate, Exp(rates(k,r)), instead of the common station rate, so
19 * unlike pfqn_stdf the heuristic accepts an FCFS station whose per-class
20 * rates differ and performs no rate-agreement check.
21 * 2. At and above the server count the Erlang residual of pfqn_stdf, which
22 * assumes one common rate, is replaced by a sum of R independent
23 * exponentials, one per class, whose means split the n - S(k) + 1 waiting
24 * jobs in proportion to the mean queue lengths Q1(k,s) of the aggregate
25 * solve: Exp( Q1(k,s) (n - S(k) + 1) / sum_s Q1(k,s) / rates(k,s) ).
26 * Q1 comes from pfqn_mvald, or, when there is a single station, from the
27 * ratio of two pfqn_comomrm_ld constants, the second evaluated on the
28 * aggregate rate lattice pfqn_mu_ms(sum(N), 2, S(k)) of two S(k)-server
29 * stations.
30 * 3. The inner constants Y_ks(t) come from the reduction heuristic pfqn_rd, not
31 * from an exact load-dependent solve, and the tilted lattice is NOT
32 * truncated before being handed over.
33 * 4. The outer constant lGk always comes from pfqn_mvald, even when there is a
34 * single station and the other constants come from pfqn_comomrm_ld.
35 * 5. There is NO min(1, .) clamp on the result. The heuristic therefore reports
36 * values above one where the approximation overshoots -- this is observed,
37 * not hypothetical, and is reproduced deliberately rather than repaired.
38 *
39 * TWO DEFECTS OF THE REFERENCE, reproduced as failures rather than as wrong
40 * numbers. Neither is repaired here, since MATLAB is the ground truth and both
41 * are failures in it too.
42 *
43 * (a) Small t. h_k(t | n) underflows to zero for the higher levels, so the
44 * tilted rate gamma_k(t, n) becomes infinite, every entry of the beta
45 * transform inside pfqn_rd becomes infinite, and pfqn_rd's
46 * `lastfinite = max(find(isfinite(...)))` is empty, whereupon
47 * `s(ist) = lastfinite` raises "Unable to perform assignment because the
48 * left and right sides have a different number of elements". The ported
49 * pfqn_rd raises NumericError("a station has no finite load-dependent
50 * rate") at the same point, which is the same condition with a diagnosis.
51 * CORRECTED, MEASURED: this note previously claimed the failure fires at
52 * the FineTol substitute for t = 0 "in every model tried", so that the
53 * heuristic could not be evaluated at t = 0 at all. That is false. For
54 * the single-delay family MATLAB returns 1.9999999767e-08 through
55 * 1.9047620835e-09 at N = 1 to 5 there and the port matches every one.
56 * The condition is real but model-dependent, not universal.
57 *
58 * (b) A class with population zero at N - e_r. Then Q1(k,s) = 0 for that
59 * class and item 2 above asks for Exp with mean 0, i.e. an infinite rate.
60 * MATLAB builds D0 = -Inf, map_cdf returns NaN, the tilt is NaN and
61 * pfqn_rd fails as in (a). This makes the heuristic unusable for every t
62 * on any model with a class whose population is one. The port throws
63 * InputError from map_exponential_mean at the point the degenerate MAP is
64 * requested, which is the earliest place the defect is detectable.
65 *
66 * Arithmetic: INEXACT BY CONSTRUCTION, gated on has_transcendental, for the
67 * reasons given in pfqn_stdf.h and additionally because pfqn_rd is itself a
68 * truncated correction series.
69 */
70
71#include <cmath>
72#include <cstddef>
73#include <vector>
74
83#include "line/num/number.h"
84#include "line/util/error.h"
85#include "line/util/matrix.h"
86
87namespace line {
88namespace pfqn {
89
90/**
91 * Heuristic sojourn-time distribution at the listed FCFS stations.
92 *
93 * Arguments as pfqn_stdf, except that `rates` may differ across classes at an
94 * analyzed station: that is the point of the heuristic.
95 */
96template <class T>
97StdfResult<T> pfqn_stdf_heur(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
98 const std::vector<int>& S,
99 const std::vector<std::size_t>& fcfsNodes, const Matrix<T>& rates,
100 const std::vector<T>& tset) {
102 "pfqn_stdf_heur requires transcendental arithmetic: the level CDFs are matrix "
103 "exponentials, the inner constants come from a truncated correction series, and "
104 "the constants are combined in the log domain");
105
106 const std::size_t M = L.rows();
107 const std::size_t R = L.cols();
108 if (R != N.size()) throw InputError("pfqn_stdf_heur: L and N disagree on the class count");
109 if (S.size() != M) throw InputError("pfqn_stdf_heur: S has the wrong length");
110 if (rates.rows() != M || rates.cols() != R)
111 throw InputError("pfqn_stdf_heur: rates has the wrong shape");
112 for (std::size_t k = 0; k < M; ++k)
113 if (S[k] < 1) throw InputError("pfqn_stdf_heur: the server count must be at least one");
114
115 int Nt = 0;
116 for (int n : N) {
117 if (n < 0) throw InputError("pfqn_stdf_heur: negative population");
118 Nt += n;
119 }
120 if (Nt < 1) throw InputError("pfqn_stdf_heur: the population must be at least one");
121
122 const T zero = num_traits<T>::from_int(0);
123 const T one = num_traits<T>::from_int(1);
125 const std::size_t nt = tset.size();
126
127 const Matrix<T> mu = detail::stdf_mu<T>(S, Nt);
128 const std::vector<T> tv = detail::stdf_guard_tset(tset);
129
130 StdfResult<T> res;
131 res.tset = tv;
132 res.RD.assign(M, std::vector<Matrix<T>>(R));
133
134 const bool singleStation = (M == 1);
135
136 for (std::size_t ki = 0; ki < fcfsNodes.size(); ++ki) {
137 const std::size_t k = fcfsNodes[ki];
138 if (k >= M) throw InputError("pfqn_stdf_heur: FCFS station index out of range");
139 if (!(rates(k, 0) > zero))
140 throw InputError("pfqn_stdf_heur: the FCFS service rate must be strictly positive");
141
142 const Matrix<T> Lk = detail::stdf_drop_row(L, k);
143 const Matrix<T> muk = detail::stdf_drop_row(mu, k);
144
145 for (std::size_t r = 0; r < R; ++r) {
146 if (!(L(k, r) > fine)) continue;
147 std::vector<int> Nr = N;
148 Nr[r] -= 1;
149 int sumNr = 0;
150 for (int n : Nr) sumNr += n;
151
152 // ---- aggregate solve: the constant and the mean queue lengths ----
153 double lGr;
154 Matrix<T> Q1(M, R, zero);
155 if (singleStation) {
156 lGr = pfqn_comomrm_ld(L, Nr, Z, mu).lG;
157 // Aggregate of two S(k)-server stations, as the reference does.
158 const std::vector<T> ms = pfqn_mu_ms<T>(Nt, 2, S[k]);
159 Matrix<T> muA(1, ms.size());
160 for (std::size_t j = 0; j < ms.size(); ++j) muA(0, j) = ms[j];
161 const double lGra = pfqn_comomrm_ld(L, Nr, Z, muA).lG;
162 const T ratio = num_traits<T>::from_double(std::exp(lGra - lGr));
163 for (std::size_t s = 0; s < R; ++s) Q1(k, s) = L(k, s) * ratio;
164 } else {
165 const MvaLdResult<T> agg = pfqn_mvald(L, Nr, Z, mu);
166 if (!agg.isNumStable) res.isNumStable = false;
167 lGr = agg.lG;
168 Q1 = agg.QN;
169 }
170
171 // ---- per-class level CDFs ---------------------------------------
172 T Q1sum = zero;
173 for (std::size_t s = 0; s < R; ++s) Q1sum += Q1(k, s);
174 Matrix<T> hkc(nt, static_cast<std::size_t>(Nt) + 1);
175 for (int n = 0; n <= Nt; ++n) {
176 mam::Map<T> h;
177 if (!(rates(k, r) > zero))
178 throw InputError("pfqn_stdf_heur: non-positive per-class service rate");
179 const T mean = T(one / rates(k, r));
180 if (n < S[k]) {
182 } else {
183 std::vector<mam::Map<T>> parts;
184 parts.push_back(mam::map_exponential_mean(mean));
185 const T lvl = num_traits<T>::from_int(n - S[k] + 1);
186 for (std::size_t s = 0; s < R; ++s)
187 parts.push_back(mam::map_exponential_mean(
188 T(Q1(k, s) * lvl / Q1sum / rates(k, s))));
189 h = mam::map_sumind(parts);
190 }
191 const std::vector<T> F = mam::map_cdf(h, tv);
192 for (std::size_t t = 0; t < nt; ++t) hkc(t, static_cast<std::size_t>(n)) = F[t];
193 }
194
195 // ---- outer constant: always the load-dependent MVA ---------------
196 const MvaLdResult<T> outer = pfqn_mvald(Lk, Nr, Z, muk);
197 const double lGk = outer.lG;
198
199 Matrix<T> RD(nt, 2);
200 for (std::size_t t = 0; t < nt; ++t) RD(t, 1) = tv[t];
201
202 Matrix<T> gammat, gammak;
203 for (std::size_t t = 0; t < nt; ++t) {
204 detail::stdf_gamma(mu, hkc, t, k, sumNr, false, gammat, gammak);
205 T H = hkc(t, 0) * num_traits<T>::from_double(std::exp(lGk));
206 for (std::size_t s = 0; s < R; ++s) {
207 if (Nr[s] <= 0) continue;
208 std::vector<int> Nrs = Nr;
209 Nrs[s] -= 1;
210 const double lY = pfqn_rd(L, Nrs, Z, gammak).lGN;
211 H += L(k, s) * hkc(t, 0) / gammat(k, 0) *
212 num_traits<T>::from_double(std::exp(lY));
213 }
214 if (!(H == H)) H = fine; // the reference's isnan guard
215 const double lH = num_traits<T>::log_as_double(H);
216 // No min(1, .): the heuristic can and does overshoot one.
217 RD(t, 0) = num_traits<T>::from_double(std::exp(lH - lGr));
218 }
219 res.RD[k][r] = RD;
220 }
221 }
222 return res;
223}
224
225} // namespace pfqn
226} // namespace line
227
228#endif // LINE_API_PFQN_STDF_HEUR_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Cumulative distribution of the inter-arrival time of a MAP.
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Map< T > map_exponential_mean(const T &mean)
Poisson process with the given mean inter-arrival time (map_exponential.m).
Map< T > map_sumind(const std::vector< Map< T > > &maps)
Sum of independent, not necessarily identical MAPs: after each component completes,...
std::vector< T > map_cdf(const Map< T > &m, const std::vector< T > &points)
Cumulative distribution of the inter-arrival time at the given points.
Definition map_cdf.h:63
RdResult< T > pfqn_rd(const Matrix< T > &L0, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu0, double tol, NcMethod method)
Reduction heuristic (RD) for the normalizing constant of a closed LOAD-DEPENDENT product-form network...
Definition pfqn_rd.h:101
MvaLdResult< T > pfqn_mvald(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu, bool stabilize=true)
Exact MVA for a closed network of load-dependent stations.
Definition pfqn_mvams.h:133
StdfResult< T > pfqn_stdf_heur(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &S, const std::vector< std::size_t > &fcfsNodes, const Matrix< T > &rates, const std::vector< T > &tset)
Heuristic sojourn-time distribution at the listed FCFS stations.
static const double kStdfFineTol
GlobalConstants.FineTol, as set by matlab/lineStart.m.
Definition pfqn_stdf.h:88
std::vector< T > pfqn_mu_ms(int N, int m, int c)
Aggregate load-dependent rate of m identical c-server FCFS stations.
Definition pfqn_mu_ms.h:56
ComomRmResult< T > pfqn_comomrm_ld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu)
CoMoM for the repairman model with an arbitrary LOAD-DEPENDENT rate lattice at the single queueing st...
Number-type abstraction for the templated API port.
CoMoM for the repairman model with an arbitrary LOAD-DEPENDENT rate lattice at the single queueing st...
Aggregate load-dependent rate of m identical c-server FCFS stations.
Shift the load-dependent service-rate lattice of selected stations.
Exact Mean Value Analysis for mixed open/closed networks with multiserver stations.
Reduction heuristic (RD) for the normalizing constant of a closed LOAD-DEPENDENT product-form network...
Sojourn-time distribution at multiserver FCFS stations of a closed product-form network (J.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Result of pfqn_mvald, mirroring the seven MATLAB outputs.
Definition pfqn_mvams.h:96
double lG
log of the normalizing constant
Definition pfqn_mvams.h:104
Matrix< T > QN
(M x R) mean queue length
Definition pfqn_mvams.h:98
bool isNumStable
false once a marginal probability had to be clamped
Definition pfqn_mvams.h:105
Result of pfqn_stdf / pfqn_stdf_heur, mirroring the MATLAB cell array RD.
Definition pfqn_stdf.h:97
std::vector< std::vector< Matrix< T > > > RD
Definition pfqn_stdf.h:98
bool isNumStable
false once an aggregate solve reported instability
Definition pfqn_stdf.h:100
std::vector< T > tset
the time set after the zero guard
Definition pfqn_stdf.h:99