LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_nc_lcfsqn.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_SOLVERS_NC_SOLVER_NC_LCFSQN_H
6#define LINE_SOLVERS_NC_SOLVER_NC_LCFSQN_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The two-station LCFS + LCFS-PR closed network. Port of `solver_nc_lcfsqn.m`.
12 *
13 * A last-come-first-served station WITHOUT preemption is not a BCMP station:
14 * the queue-length distribution depends on the order of arrival, so no
15 * product form and no ordinary normalizing constant exist for it. The pair
16 * LCFS / LCFS-PR does admit a closed form (Casale, QUESTA 2026) whose constant
17 * `pfqn_lcfsqn_ca` computes by recursion, and whose per-class measures are sums
18 * of PERMANENTS over the boundary position between the two stations.
19 *
20 * THE PERMANENT FORMULAS ASSUME ONE JOB PER CLASS. A class with N_r > 1 is
21 * expanded into N_r exchangeable single-job copies, the constant is rescaled to
22 * the distinguishable-jobs one G_exp = G prod_r N_r!, and the per-copy measures
23 * are scaled back by N_r. This is what makes the routine cost
24 * (sum N)! -- exponential in the population -- so it is a special-case analyzer,
25 * not a general one.
26 *
27 * Arithmetic: the permanents are exact-capable (`pfqn_perm` uses the integer
28 * Pascal recurrence), but lG is a log, so the analyzer is guarded on
29 * `has_transcendental` for that alone.
30 */
31
32#include <cmath>
33#include <vector>
34
39#include "line/util/error.h"
40
41namespace line {
42namespace nc {
43
44namespace detail {
45
46/**
47 * `make_Tx`: the (K-1 x K-1) throughput matrix at boundary position xt, with
48 * the expanded copy r removed. Row i is the copy, column j the position.
49 */
50template <class T>
51Matrix<T> lcfs_make_Tx(const std::vector<T>& alpha, const std::vector<T>& beta, std::size_t xt,
52 std::size_t K, std::size_t r) {
53 const T zero = num_traits<T>::from_int(0);
54 Matrix<T> Tx(K - 1, K - 1, zero);
55 std::size_t idx = 0;
56 for (std::size_t i = 0; i < K; ++i) {
57 if (i + 1 == r) continue;
58 for (std::size_t j = 1; j <= xt - 1; ++j) Tx(idx, j - 1) = num_pow_int(alpha[i], j);
59 for (std::size_t j = 1; j <= K - xt; ++j)
60 Tx(idx, xt - 2 + j) = T(num_pow_int(alpha[i], xt + j - 1) * beta[i]);
61 ++idx;
62 }
63 return Tx;
64}
65
66/**
67 * `make_Yx`: the (K x K) queue-length matrix at boundary position xt. The
68 * LCFS-PR side of copy r is left at zero, which is what makes the permanent
69 * count the states holding that copy at station 1.
70 */
71template <class T>
72Matrix<T> lcfs_make_Yx(const std::vector<T>& alpha, const std::vector<T>& beta, std::size_t xt,
73 std::size_t K, std::size_t r) {
74 const T zero = num_traits<T>::from_int(0);
75 Matrix<T> Y(K, K, zero);
76 for (std::size_t i = 0; i < K; ++i) {
77 for (std::size_t j = 1; j <= xt; ++j) Y(i, j - 1) = num_pow_int(alpha[i], j);
78 for (std::size_t j = 1; j <= K - xt; ++j)
79 if (i + 1 != r) Y(i, xt + j - 1) = T(num_pow_int(alpha[i], xt + j - 1) * beta[i]);
80 }
81 return Y;
82}
83
84} // namespace detail
85
86/**
87 * Port of `solver_nc_lcfsqn.m`.
88 *
89 * @param sn the refreshed struct
90 * @param opt solver controls; unused, the closed form has no tuning
91 * @param lcfsStat 1-based index of the LCFS station
92 * @param lcfsprStat 1-based index of the LCFS-PR station
93 */
94template <class T>
96 std::size_t lcfsStat, std::size_t lcfsprStat) {
97 (void)opt;
98 NcSolution<T> out;
99 if constexpr (!num_traits<T>::has_transcendental) {
100 (void)sn;
101 (void)lcfsStat;
102 (void)lcfsprStat;
103 throw UnsupportedError(
104 "solver_nc_lcfsqn: the LCFS closed form reports lG = log(G); this backend has no "
105 "transcendental arithmetic");
106 } else {
107 const T zero = num_traits<T>::from_int(0);
108 const std::size_t M = sn.nstations, R = sn.nclasses;
109
110 std::vector<T> alpha(R, zero), beta(R, zero);
111 std::vector<int> N(R, 0);
112 for (std::size_t r = 0; r < R; ++r) {
113 if (std::isinf(sn.classes[r].population))
114 throw UnsupportedError("solver_nc_lcfsqn: requires a closed queueing network");
115 N[r] = static_cast<int>(std::llround(sn.classes[r].population));
116 if (N[r] <= 0) continue;
117 const double mu_l = num_traits<T>::to_double(sn.rates(lcfsStat - 1, r));
118 const double mu_p = num_traits<T>::to_double(sn.rates(lcfsprStat - 1, r));
119 if (!(mu_l > 0.0) || !std::isfinite(mu_l))
120 throw UnsupportedError(
121 "solver_nc_lcfsqn: invalid service rate at the LCFS station");
122 if (!(mu_p > 0.0) || !std::isfinite(mu_p))
123 throw UnsupportedError(
124 "solver_nc_lcfsqn: invalid service rate at the LCFS-PR station");
125 alpha[r] = T(num_traits<T>::from_int(1) / sn.rates(lcfsStat - 1, r));
126 beta[r] = T(num_traits<T>::from_int(1) / sn.rates(lcfsprStat - 1, r));
127 }
128
129 const T G = pfqn::pfqn_lcfsqn_ca(alpha, beta, N).G;
130 out.sol.lG = G > zero ? num_traits<T>::log_as_double(G)
131 : -std::numeric_limits<double>::infinity();
132
133 std::size_t K = 0;
134 for (int v : N) K += static_cast<std::size_t>(v);
135
136 // expand each class into N_r exchangeable single-job copies
137 std::vector<T> alphaE, betaE;
138 std::vector<std::size_t> ecls(R, 0);
139 for (std::size_t r = 0; r < R; ++r) {
140 ecls[r] = alphaE.size() + 1;
141 for (int a = 0; a < N[r]; ++a) {
142 alphaE.push_back(alpha[r]);
143 betaE.push_back(beta[r]);
144 }
145 }
146 T Gexp = G;
147 for (std::size_t r = 0; r < R; ++r)
148 Gexp = T(Gexp * num_factorial<T>(static_cast<unsigned>(N[r])));
149
150 const std::vector<int> ones(K, 1);
151 Matrix<T> Q_l(2, R, zero), U_l(2, R, zero), T_l(2, R, zero);
152 for (std::size_t r = 0; r < R; ++r) {
153 if (N[r] <= 0) continue;
154 const std::size_t e = ecls[r];
155 T Tcopy = zero, Qcopy = zero;
156 for (std::size_t xt = 1; xt <= K; ++xt) {
157 const Matrix<T> Tx = detail::lcfs_make_Tx(alphaE, betaE, xt, K, e);
158 const std::vector<int> onesTx(K - 1, 1);
159 Tcopy = T(Tcopy + num_pow_int(alphaE[e - 1], xt - 1) *
160 pfqn::pfqn_perm(Tx, onesTx) / Gexp);
161 const Matrix<T> Yx = detail::lcfs_make_Yx(alphaE, betaE, xt, K, e);
162 Qcopy = T(Qcopy + pfqn::pfqn_perm(Yx, ones) / Gexp);
163 }
164 const T nr = num_traits<T>::from_int(N[r]);
165 T_l(0, r) = T(nr * Tcopy);
166 T_l(1, r) = T(nr * Tcopy);
167 Q_l(0, r) = T(nr * Qcopy);
168 Q_l(1, r) = T(nr - Q_l(0, r)); // conservation at the LCFS-PR station
169 }
170 for (std::size_t r = 0; r < R; ++r) {
171 U_l(0, r) = T(T_l(0, r) * alpha[r]);
172 U_l(1, r) = T(T_l(1, r) * beta[r]);
173 }
174
175 Matrix<T> Q(M, R, zero), U(M, R, zero), Tp(M, R, zero), Rt(M, R, zero);
176 std::vector<T> X(R, zero), C(R, zero);
177 for (std::size_t r = 0; r < R; ++r) {
178 Q(lcfsStat - 1, r) = Q_l(0, r);
179 Q(lcfsprStat - 1, r) = Q_l(1, r);
180 U(lcfsStat - 1, r) = U_l(0, r);
181 U(lcfsprStat - 1, r) = U_l(1, r);
182 if (N[r] <= 0) continue;
183 X[r] = T_l(0, r);
184 Tp(lcfsStat - 1, r) = T_l(0, r);
185 Tp(lcfsprStat - 1, r) = T_l(1, r);
186 }
187 for (std::size_t i : {lcfsStat, lcfsprStat})
188 for (std::size_t r = 0; r < R; ++r)
189 if (Tp(i - 1, r) > zero) Rt(i - 1, r) = T(Q(i - 1, r) / Tp(i - 1, r));
190 for (std::size_t r = 0; r < R; ++r)
191 if (N[r] > 0) C[r] = T(Rt(lcfsStat - 1, r) + Rt(lcfsprStat - 1, r));
192
193 out.sol.Q = Q;
194 out.sol.U = U;
195 out.sol.R = Rt;
196 out.sol.Tp = Tp;
197 out.sol.X = X;
198 out.sol.C = C;
199 out.sol.iter = 1;
200 out.sol.method = "lcfsqn.ca";
201 out.actualmethod = "lcfsqn.ca";
202 return out;
203 }
204}
205
206} // namespace nc
207} // namespace line
208
209#endif // LINE_SOLVERS_NC_SOLVER_NC_LCFSQN_H
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
The exception types the port throws.
NcSolution< T > solver_nc_lcfsqn(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t lcfsStat, std::size_t lcfsprStat)
Port of solver_nc_lcfsqn.m.
T pfqn_perm(const Matrix< T > &A, const std::vector< int > &m)
Permanent of a matrix with repeated columns, by Ryser's formula.
Definition pfqn_perm.h:52
LcfsQnResult< T > pfqn_lcfsqn_ca(const std::vector< T > &alpha, const std::vector< T > &beta, const std::vector< int > &N)
Convolution algorithm for the two-station multiclass LCFS queueing network of Casale,...
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Definition linalg.h:104
Controls and result shape shared by the normalizing-constant analyzers.
A queueing network and its refreshed NetworkStruct.
Convolution algorithm for the two-station multiclass LCFS queueing network of Casale,...
Permanent of a matrix with repeated columns, by Ryser's formula.
The [Q,U,R,T,C,X,lG] of the reference, plus the algorithm that ran.
Definition nc_types.h:113
mva::MvaSolution< T > sol
Definition nc_types.h:114
std::string actualmethod
Definition nc_types.h:115
Controls, defaulting to SolverOptions('NC') in the reference.
Definition nc_types.h:33