LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_lcfsqn_nc.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_NC_H
6#define LINE_API_PFQN_LCFSQN_NC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Normalizing constant of the two-station multiclass LCFS network as a sum of
12 * PERMANENTS, the closed form of Casale, QUESTA 2026.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_lcfsqn_nc.m.
15 *
16 * Splitting the job sequence at the position x of the boundary between the
17 * two stations gives
18 *
19 * G(N) = ( sum_{x=0}^{K} perm(A_x, N) ) / prod_r N_r!, K = sum_r N_r,
20 *
21 * where A_x is the (K x R) matrix of the per-position, per-class weights
22 *
23 * A_x(i,r) = alpha_r^i for i <= x (LCFS side)
24 * A_x(i,r) = alpha_r^{i-1} beta_r for i > x (LCFS-PR side)
25 *
26 * and the permanent is taken with column multiplicities N.
27 *
28 * TWO REFERENCE DEFECTS, both corrected here, both invisible at the default
29 * population N = ones(1,R) and both silent for any other one.
30 *
31 * 1. TRANSPOSED MATRIX. MATLAB's make_A builds the matrix transposed with
32 * respect to the way perm() then reads it: make_A fills A(class, position)
33 * over i = 1..R and j = 1..K, while perm(A, N) reads A(position, class)
34 * over i = 1..sum(N) and k = 1..R. The two indexings coincide only when
35 * K = R, i.e. exactly the default. For any larger population the rows
36 * i = R+1, ..., K of the matrix perm() actually reads are identically zero,
37 * so every product term in Ryser's formula contains a zero factor and the
38 * routine returns G = 0 -- silently, since zero is a representable constant
39 * and nothing downstream checks positivity.
40 *
41 * 2. MISSING prod_r N_r!. The permanent counts the ORDERED arrangements of the
42 * jobs, so it over-counts every state by the number of permutations WITHIN
43 * each class. The same division appears explicitly in the sibling routine
44 * pfqn_joint, whose local Fper ends in `perm(A)/prod(factorial(N))`; it is
45 * absent here. With N = ones the factorials are all 1, which is why the
46 * omission never showed.
47 *
48 * Both corrections are verified, not assumed: with them the closed form agrees
49 * with pfqn_lcfsqn_ca -- an independent recursion sharing no code -- as an
50 * EXACT rational on every population tested, and without either one it does
51 * not.
52 *
53 * Arithmetic: EXACT-CAPABLE, inheriting exactness from pfqn_perm. Note that
54 * the cost is (K+1) * prod_r (N_r + 1) * K * R, so this form is a closed-form
55 * cross-check on the recursion of pfqn_lcfsqn_ca rather than a replacement
56 * for it.
57 */
58
59#include <cstddef>
60#include <vector>
61
63#include "line/num/number.h"
64#include "line/util/error.h"
65#include "line/util/matrix.h"
66
67namespace line {
68namespace pfqn {
69
70/**
71 * @brief Normalizing constant of the two-station multiclass LCFS network as a
72 * sum of PERMANENTS, the closed form of Casale, QUESTA 2026.
73 *
74 * @param alpha (R) mean service times at the LCFS station
75 * @param beta (R) mean service times at the LCFS-PR station
76 * @param N (R) population per class
77 */
78template <class T>
79T pfqn_lcfsqn_nc(const std::vector<T>& alpha, const std::vector<T>& beta,
80 const std::vector<int>& N) {
81 const std::size_t R = alpha.size();
82 if (beta.size() != R) throw InputError("pfqn_lcfsqn_nc: alpha and beta have different lengths");
83 if (N.size() != R) throw InputError("pfqn_lcfsqn_nc: alpha and N have different lengths");
84
85 const T one = num_traits<T>::from_int(1);
87 int K = 0;
88 for (int v : N) {
89 if (v < 0) throw InputError("pfqn_lcfsqn_nc: negative population");
90 K += v;
91 }
92 if (K == 0) return one;
93
94 for (int x = 0; x <= K; ++x) {
95 Matrix<T> A(static_cast<std::size_t>(K), R);
96 for (int i = 1; i <= K; ++i)
97 for (std::size_t r = 0; r < R; ++r)
98 A(static_cast<std::size_t>(i - 1), r) =
99 i <= x ? num_pow_int(alpha[r], static_cast<unsigned>(i))
100 : num_pow_int(alpha[r], static_cast<unsigned>(i - 1)) * beta[r];
101 G += pfqn_perm(A, N);
102 }
103 for (int v : N) G /= num_factorial<T>(static_cast<unsigned>(v));
104 return G;
105}
106
107} // namespace pfqn
108} // namespace line
109
110#endif // LINE_API_PFQN_LCFSQN_NC_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
T pfqn_lcfsqn_nc(const std::vector< T > &alpha, const std::vector< T > &beta, const std::vector< int > &N)
Normalizing constant of the two-station multiclass LCFS network as a sum of PERMANENTS,...
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
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
Number-type abstraction for the templated API port.
Permanent of a matrix with repeated columns, by Ryser's formula.