LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_sqni.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_PFQN_SQNI_H
6#define LINE_API_PFQN_PFQN_SQNI_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Square-root non-iterative (SQNI) approximation for a single queueing station
12 * with per-class delay.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_sqni.m. Each class throughput is
15 * the admissible root of a quadratic assembled from the balanced-job estimate
16 * B_r of the other classes' contribution:
17 *
18 * X_r = (Z_r - sqrt(disc) - B_r + L_r Ntot) / (2 L_r Z_r)
19 * disc = B_r^2 - 2 B_r L_r Ntot - 2 B_r Z_r + L_r^2 Ntot^2 + 2 L_r Ntot Z_r
20 * - 4 N_r L_r Z_r + Z_r^2
21 *
22 * with the discriminant clamped at zero, as in the reference. Classes with
23 * Z_r = 0 (self-looping classes, whose quadratic is degenerate) are handled by
24 * the reference's two-pass structure: their queue length is pre-set to N_r,
25 * and their throughput is filled in from the total queue length afterwards.
26 *
27 * ARITHMETIC. The square root is essential, so the routine is gated on
28 * num_traits<T>::has_transcendental.
29 */
30
31#include <cmath>
32#include <cstddef>
33#include <vector>
34
35#include "line/num/number.h"
36#include "line/util/error.h"
37
38namespace line {
39namespace pfqn {
40
41/** Return value of pfqn_sqni, mirroring [Q, U, X] for the single station. */
42template <class T>
43struct SqniResult {
44 std::vector<T> Q;
45 std::vector<T> U;
46 std::vector<T> X;
47};
48
49/**
50 * @brief Square-root non-iterative (SQNI) approximation for a single queueing
51 * station with per-class delay.
52 *
53 * @param N (R) population, @param L (R) demand at the station,
54 * @param Z (R) think times
55 */
56template <class T>
57SqniResult<T> pfqn_sqni(const std::vector<T>& N, const std::vector<T>& L, const std::vector<T>& Z) {
59 "pfqn_sqni requires transcendental arithmetic (the throughput solves a quadratic)");
60 using std::sqrt;
61 const std::size_t C = L.size();
62 if (N.size() != C || Z.size() != C)
63 throw InputError("pfqn_sqni: N, L and Z must have the same length");
64 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
65 const T two = num_traits<T>::from_int(2), four = num_traits<T>::from_int(4);
66
68 r.Q.assign(C, zero);
69 r.U.assign(C, zero);
70 r.X.assign(C, zero);
71 T Nt = zero;
72 for (const T& v : N) Nt += v;
73 if (Nt <= zero) return r;
74
75 if (Nt == one) {
76 for (std::size_t s = 0; s < C; ++s) {
77 const T den = T(Z[s] + L[s]);
78 if (den == zero) throw NumericError("pfqn_sqni: zero cycle time");
79 r.X[s] = T(N[s] / den);
80 r.U[s] = T(r.X[s] * L[s]);
81 r.Q[s] = r.U[s];
82 }
83 return r;
84 }
85
86 for (std::size_t s = 0; s < C; ++s)
87 if (Z[s] == zero) r.Q[s] = N[s];
88
89 for (std::size_t s = 0; s < C; ++s) {
90 if (Z[s] == zero) continue; // handled after the main loop
91 const T Nr = N[s], Lr = L[s], Zr = Z[s];
92 // B_r: balanced-job estimate of the delay-resident population of the
93 // other classes, at the population with one class-r job removed.
94 T inner = zero;
95 for (std::size_t t = 0; t < C; ++t) {
96 T N1 = N[t];
97 if (t == s) N1 -= one;
98 const T d = T(Z[t] + L[t] + L[t] * T(Nt - two));
99 if (d == zero) continue;
100 inner += T(Z[t] * N1 / d);
101 }
102 T Brsum = zero;
103 for (std::size_t t = 0; t < C; ++t) {
104 if (t == s) continue;
105 const T d = T(Z[t] + L[t] + L[t] * T(Nt - one - inner));
106 if (d == zero) continue;
107 Brsum += T(N[t] / d * Z[t]);
108 }
109 const T Br = T(Lr * Brsum);
110 if (Lr == zero) {
111 r.X[s] = T(Nr / Zr);
112 } else {
113 T disc = T(Br * Br - two * Br * Lr * Nt - two * Br * Zr + Lr * Lr * Nt * Nt +
114 two * Lr * Nt * Zr - four * Nr * Lr * Zr + Zr * Zr);
115 if (disc < zero) disc = zero;
116 r.X[s] = T(T(Zr - sqrt(disc) - Br + Lr * Nt) / T(two * Lr * Zr));
117 }
118 r.U[s] = T(r.X[s] * L[s]);
119 r.Q[s] = T(N[s] - r.X[s] * Z[s]);
120 }
121
122 T Qtot = zero;
123 for (const T& v : r.Q) Qtot += v;
124 for (std::size_t s = 0; s < C; ++s) {
125 if (Z[s] != zero) continue;
126 if (L[s] == zero) throw NumericError("pfqn_sqni: a class has neither demand nor think time");
127 r.X[s] = T(N[s] / T(L[s] * T(one + Qtot)));
128 r.U[s] = T(r.X[s] * L[s]);
129 r.Q[s] = T(N[s] - r.X[s] * Z[s]);
130 }
131 return r;
132}
133
134} // namespace pfqn
135} // namespace line
136
137#endif // LINE_API_PFQN_PFQN_SQNI_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
SqniResult< T > pfqn_sqni(const std::vector< T > &N, const std::vector< T > &L, const std::vector< T > &Z)
Square-root non-iterative (SQNI) approximation for a single queueing station with per-class delay.
Definition pfqn_sqni.h:57
Number-type abstraction for the templated API port.
Return value of pfqn_sqni, mirroring [Q, U, X] for the single station.
Definition pfqn_sqni.h:43
std::vector< T > X
Definition pfqn_sqni.h:46
std::vector< T > Q
Definition pfqn_sqni.h:44
std::vector< T > U
Definition pfqn_sqni.h:45