LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_oi_insvc.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_OI_INSVC_H
6#define LINE_API_PFQN_PFQN_OI_INSVC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Conditional mean number of IN-SERVICE jobs per class at an order-independent
12 * station.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_oi_insvc.m. This is the quantity
15 * behind LINE's utilization convention at OI stations, U_r = E[sir_r]/c, where
16 * sir_r counts class-r JOBS receiving a strictly positive rate (a job served
17 * concurrently by several servers counts once). Conditioning on the tail
18 * element of the ordering gives the balanced-fairness recursion for the OI
19 * balance function and its sir-weighted companion,
20 *
21 * Phi(0) = 1, Phi(n) = (1/mu(n)) sum_{r: n_r>0} Phi(n - e_r)
22 * Xi_r(0) = 0,
23 * Xi_r(n) = (1/mu(n)) [ sum_s Xi_r(n - e_s)
24 * + 1{n_r>0} 1{mu(n) > mu(n - e_r)} Phi(n - e_r) ],
25 *
26 * and E[sir_r | n] = Xi_r(n)/Phi(n).
27 *
28 * UNREACHABLE STATES. A composition no server can serve has mu(n) <= 0; the
29 * reference leaves Phi and Xi at zero there and the port does the same, rather
30 * than dividing by zero or substituting a rate. g is then zero at that state,
31 * which is the correct reading: the state carries no weight.
32 *
33 * ARITHMETIC. Additions and divisions only, so the routine is EXACT in
34 * rational arithmetic and is deliberately left ungated. The strict comparison
35 * mu(n) > mu(n - e_r) that decides whether the tail job is in service is an
36 * exact comparison there, which matters: in floating point two rates that are
37 * equal in the model can differ in the last bit and flip that indicator.
38 */
39
40#include <cstddef>
41#include <functional>
42#include <vector>
43
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace pfqn {
50
51/** Return value of pfqn_oi_insvc, mirroring [g, Xi, Phi]. */
52template <class T>
54 Matrix<T> g; ///< (prod(N+1) x R) E[sir_r | n]
55 Matrix<T> Xi; ///< (prod(N+1) x R) sir-weighted balance
56 std::vector<T> Phi; ///< (prod(N+1)) OI balance function
57 std::vector<std::size_t> stride; ///< column-major strides, for indexing
58};
59
60/**
61 * @brief Conditional mean number of IN-SERVICE jobs per class at an
62 * order-independent station.
63 *
64 * @param oirate OI total service rate mu(n) for a per-class count vector
65 * @param N (R) closed population vector
66 */
67template <class T>
68OiInsvcResult<T> pfqn_oi_insvc(const std::function<T(const std::vector<int>&)>& oirate,
69 const std::vector<int>& N) {
70 if (!oirate) throw InputError("pfqn_oi_insvc: oirate must be callable");
71 const std::size_t R = N.size();
72 if (R == 0) throw InputError("pfqn_oi_insvc: empty population vector");
73 std::vector<std::size_t> shp(R), stride(R, 1);
74 std::size_t total = 1;
75 for (std::size_t d = 0; d < R; ++d) {
76 if (N[d] < 0) throw InputError("pfqn_oi_insvc: negative population");
77 shp[d] = static_cast<std::size_t>(N[d]) + 1;
78 }
79 for (std::size_t d = 1; d < R; ++d) stride[d] = stride[d - 1] * shp[d - 1];
80 for (std::size_t d = 0; d < R; ++d) total *= shp[d];
81
82 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
83 std::vector<std::vector<int>> subs(total, std::vector<int>(R, 0));
84 for (std::size_t i = 0; i < total; ++i) {
85 std::size_t li = i;
86 for (std::size_t d = 0; d < R; ++d) {
87 subs[i][d] = static_cast<int>(li % shp[d]);
88 li /= shp[d];
89 }
90 }
91 std::vector<T> muv(total, zero);
92 for (std::size_t i = 0; i < total; ++i) {
93 int tot = 0;
94 for (int v : subs[i]) tot += v;
95 if (tot > 0) muv[i] = oirate(subs[i]);
96 }
97
98 std::vector<T> Phi(total, zero);
99 Matrix<T> Xi(total, R, zero);
100 for (std::size_t i = 0; i < total; ++i) {
101 const std::vector<int>& n = subs[i];
102 int tot = 0;
103 for (int v : n) tot += v;
104 if (tot == 0) {
105 Phi[i] = one;
106 continue;
107 }
108 const T mun = muv[i];
109 if (mun <= zero) continue; // unreachable state, zero weight
110 T sPhi = zero;
111 std::vector<T> sXi(R, zero);
112 for (std::size_t s = 0; s < R; ++s) {
113 if (n[s] <= 0) continue;
114 const std::size_t j = i - stride[s];
115 sPhi += Phi[j];
116 for (std::size_t r = 0; r < R; ++r) sXi[r] += Xi(j, r);
117 }
118 Phi[i] = T(sPhi / mun);
119 for (std::size_t r = 0; r < R; ++r) {
120 T acc = sXi[r];
121 if (n[r] > 0) {
122 const std::size_t j = i - stride[r];
123 if (mun > muv[j]) acc += Phi[j]; // the tail class-r job is in service
124 }
125 Xi(i, r) = T(acc / mun);
126 }
127 }
128
130 res.g = Matrix<T>(total, R, zero);
131 for (std::size_t i = 0; i < total; ++i)
132 if (Phi[i] > zero)
133 for (std::size_t r = 0; r < R; ++r) res.g(i, r) = T(Xi(i, r) / Phi[i]);
134 res.Xi = Xi;
135 res.Phi = Phi;
136 res.stride = stride;
137 return res;
138}
139
140} // namespace pfqn
141} // namespace line
142
143#endif // LINE_API_PFQN_PFQN_OI_INSVC_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
OiInsvcResult< T > pfqn_oi_insvc(const std::function< T(const std::vector< int > &)> &oirate, const std::vector< int > &N)
Conditional mean number of IN-SERVICE jobs per class at an order-independent station.
Number-type abstraction for the templated API port.
Return value of pfqn_oi_insvc, mirroring [g, Xi, Phi].
std::vector< std::size_t > stride
column-major strides, for indexing
std::vector< T > Phi
(prod(N+1)) OI balance function
Matrix< T > g
(prod(N+1) x R) E[sir_r | n]
Matrix< T > Xi
(prod(N+1) x R) sir-weighted balance