LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_lcfsqn_mva.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_LCFSQN_MVA_H
6#define LINE_API_PFQN_LCFSQN_MVA_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Exact mean value analysis of the two-station multiclass LCFS network of
12 * Casale, QUESTA 2026 (station 1 LCFS, station 2 LCFS-PR).
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_lcfsqn_mva.m.
15 *
16 * The recursion over the population lattice carries, besides the queue lengths
17 * Q and the throughputs T, the BACK PROBABILITIES B(s,r) that a class-r job
18 * sits at the back of the queue at station s. Writing |n| for the total
19 * population, n_k for the class-k population and A = prod_r alpha_r^{n_r},
20 *
21 * Wnp = A [ 1 + Q_{n-e_k}(1,k)
22 * + sum_{r != k} (alpha_k/alpha_r) B_{n-e_k}(1,r)/B_{n-e_r}(1,k)
23 * Q_{n-e_r}(1,k) ]
24 * Wpr = alpha_k^{|n|-1} beta_k [ 1 + Q_{n-e_k}(2,k)
25 * + sum_{r != k} (alpha_r/alpha_k) B_{n-e_k}(2,r)/B_{n-e_r}(2,k)
26 * Q_{n-e_r}(2,k) ]
27 * B_n(1,k) = A n_k / (Wnp + Wpr), B_n(2,k) = alpha_k^{|n|-1} beta_k n_k / (Wnp + Wpr)
28 * Q_n(s,k) = B_n(s,k) + sum_r B_n(s,r) Q_{n-e_r}(s,k)
29 * T_n(k) = sum_r B_n(1,r) T_{n-e_r}(k) + (1/alpha_k) B_n(1,k) (1 - sum_r alpha_r T_{n-e_k}(r))
30 *
31 * with everything zero at n = 0. The reference special-cases |n| = 1; it is
32 * not necessary, since the general step reduces to it (the r != k sum is empty
33 * and every n - e_k term is zero), and the port therefore uses one uniform
34 * recursion.
35 *
36 * Arithmetic: EXACT-CAPABLE. The reference is written entirely in a
37 * scaled-log representation -- B is stored as a mantissa plus a log scale, and
38 * every sum goes through a log-sum-exp helper -- and its own comments state
39 * that this is a range device and "mathematically EXACT, no approximations are
40 * made". That representation is dropped here: the recursion is evaluated
41 * directly in T, which needs no scaling in an exact field and none in the
42 * high-precision floats either.
43 *
44 * ONE SEMANTIC CONSEQUENCE of dropping it. The log-sum-exp helper cannot
45 * represent a negative term, so the reference silently skips the throughput
46 * contribution whenever (1 - sum_r alpha_r T_{n-e_k}(r)) is negative, and
47 * skips any term whose factors are not strictly positive. Those guards are
48 * artifacts of the representation, not of the model: the bracket is a
49 * probability complement and is nonnegative on a consistent state. This port
50 * includes every term unconditionally, which is what the recursion says.
51 */
52
53#include <cstddef>
54#include <vector>
55
56#include "line/num/number.h"
57#include "line/util/error.h"
58#include "line/util/matrix.h"
60
61namespace line {
62namespace pfqn {
63
64template <class T>
66 std::vector<T> T_; ///< (R) per-class throughput
67 Matrix<T> Q; ///< (2 x R) mean queue lengths
68 Matrix<T> U; ///< (2 x R) utilizations
69 Matrix<T> B; ///< (2 x R) back probabilities
70};
71
72/**
73 * @brief Exact mean value analysis of the two-station multiclass LCFS network
74 * of Casale, QUESTA 2026 (station 1 LCFS, station 2 LCFS-PR).
75 *
76 * @param alpha (R) mean service times at the LCFS station
77 * @param beta (R) mean service times at the LCFS-PR station
78 * @param N (R) population per class
79 */
80template <class T>
81LcfsMvaResult<T> pfqn_lcfsqn_mva(const std::vector<T>& alpha, const std::vector<T>& beta,
82 const std::vector<int>& N) {
83 const std::size_t R = alpha.size();
84 if (beta.size() != R) throw InputError("pfqn_lcfsqn_mva: alpha and beta have different lengths");
85 if (N.size() != R) throw InputError("pfqn_lcfsqn_mva: alpha and N have different lengths");
86
87 const T zero = num_traits<T>::from_int(0);
88 const T one = num_traits<T>::from_int(1);
89
91 res.T_.assign(R, zero);
92 res.Q = Matrix<T>(2, R, zero);
93 res.U = Matrix<T>(2, R, zero);
94 res.B = Matrix<T>(2, R, zero);
95
96 long K = 0;
97 for (int v : N) {
98 if (v < 0) throw InputError("pfqn_lcfsqn_mva: negative population");
99 K += v;
100 }
101 if (K == 0) return res;
102 for (std::size_t r = 0; r < R; ++r)
103 if (!(alpha[r] > zero)) throw InputError("pfqn_lcfsqn_mva: a service time at the LCFS "
104 "station is not positive");
105
106 const std::vector<std::size_t> prods = plane_sizes(N);
107 const std::size_t total = population_count(N);
108 // Per state: Q(2R), B(2R), Tp(R), laid out contiguously.
109 std::vector<T> Q(total * 2 * R, zero), B(total * 2 * R, zero), Tp(total * R, zero);
110 const auto QB = [&](std::vector<T>& v, std::size_t idx, std::size_t s, std::size_t r) -> T& {
111 return v[idx * 2 * R + s * R + r];
112 };
113
114 std::vector<int> n(R, 0);
115 bool more = next_pop(n, N); // the origin stays all-zero
116 while (more) {
117 const std::size_t idx = pop_index(n, prods);
118 int tot = 0;
119 for (int v : n) tot += v;
120
121 // A = prod_r alpha_r^{n_r}, shared by every class at this state.
122 T A = one;
123 for (std::size_t r = 0; r < R; ++r)
124 A *= num_pow_int(alpha[r], static_cast<unsigned>(n[r]));
125
126 // ---- back probabilities -------------------------------------------
127 for (std::size_t k = 0; k < R; ++k) {
128 if (n[k] == 0) continue;
129 const std::size_t ik = idx - prods[k];
130 T wnp = one + QB(Q, ik, 0, k);
131 T wpr = one + QB(Q, ik, 1, k);
132 for (std::size_t r = 0; r < R; ++r) {
133 if (r == k || n[r] == 0) continue;
134 const std::size_t ir = idx - prods[r];
135 const T dnp = QB(B, ir, 0, k);
136 const T dpr = QB(B, ir, 1, k);
137 if (dnp == zero || dpr == zero)
138 throw NumericError("pfqn_lcfsqn_mva: a back probability vanished");
139 wnp += alpha[k] / alpha[r] * (QB(B, ik, 0, r) / dnp) * QB(Q, ir, 0, k);
140 wpr += alpha[r] / alpha[k] * (QB(B, ik, 1, r) / dpr) * QB(Q, ir, 1, k);
141 }
142 const T Apr = num_pow_int(alpha[k], static_cast<unsigned>(tot - 1)) * beta[k];
143 const T W = A * wnp + Apr * wpr;
144 if (W == zero) throw NumericError("pfqn_lcfsqn_mva: zero total waiting time");
145 const T nk = num_traits<T>::from_int(n[k]);
146 QB(B, idx, 0, k) = A * nk / W;
147 QB(B, idx, 1, k) = Apr * nk / W;
148 }
149
150 // ---- queue lengths -------------------------------------------------
151 for (std::size_t k = 0; k < R; ++k) {
152 if (n[k] == 0) continue;
153 for (std::size_t s = 0; s < 2; ++s) {
154 T q = QB(B, idx, s, k);
155 for (std::size_t r = 0; r < R; ++r) {
156 if (n[r] == 0) continue;
157 q += QB(B, idx, s, r) * QB(Q, idx - prods[r], s, k);
158 }
159 QB(Q, idx, s, k) = q;
160 }
161 }
162
163 // ---- throughputs ---------------------------------------------------
164 for (std::size_t k = 0; k < R; ++k) {
165 if (n[k] == 0) continue;
166 const std::size_t ik = idx - prods[k];
167 T unp = zero;
168 for (std::size_t r = 0; r < R; ++r) unp += alpha[r] * Tp[ik * R + r];
169 T t = QB(B, idx, 0, k) * (one - unp) / alpha[k];
170 for (std::size_t r = 0; r < R; ++r) {
171 if (n[r] == 0) continue;
172 t += QB(B, idx, 0, r) * Tp[(idx - prods[r]) * R + k];
173 }
174 Tp[idx * R + k] = t;
175 }
176
177 more = next_pop(n, N);
178 }
179
180 const std::size_t last = total - 1;
181 for (std::size_t r = 0; r < R; ++r) {
182 res.T_[r] = Tp[last * R + r];
183 for (std::size_t s = 0; s < 2; ++s) {
184 res.Q(s, r) = QB(Q, last, s, r);
185 res.B(s, r) = QB(B, last, s, r);
186 }
187 res.U(0, r) = res.T_[r] * alpha[r];
188 res.U(1, r) = res.T_[r] * beta[r];
189 }
190 return res;
191}
192
193} // namespace pfqn
194} // namespace line
195
196#endif // LINE_API_PFQN_LCFSQN_MVA_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.
Dense matrix and non-owning view.
LcfsMvaResult< T > pfqn_lcfsqn_mva(const std::vector< T > &alpha, const std::vector< T > &beta, const std::vector< int > &N)
Exact mean value analysis of the two-station multiclass LCFS network of Casale, QUESTA 2026 (station ...
std::size_t population_count(const std::vector< int > &N)
Number of population vectors n with 0 <= n <= N.
Definition population.h:38
std::vector< std::size_t > plane_sizes(const std::vector< int > &N)
Mixed-radix plane sizes: prods[r] = prod_{s<r} (N[s]+1).
Definition population.h:27
bool next_pop(std::vector< int > &n, const std::vector< int > &N)
Advance n to the next population vector in the lattice 0 <= n <= N, odometer order with the last clas...
Definition population.h:56
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
std::size_t pop_index(const std::vector< int > &n, const std::vector< std::size_t > &prods)
Index of n in the lattice, 0-based (MATLAB hashpop is 1-based).
Definition population.h:45
Number-type abstraction for the templated API port.
Population-vector enumeration and combinatorics.
Matrix< T > Q
(2 x R) mean queue lengths
std::vector< T > T_
(R) per-class throughput
Matrix< T > B
(2 x R) back probabilities
Matrix< T > U
(2 x R) utilizations