LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_pas_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_PAS_NC_H
6#define LINE_API_PFQN_PAS_NC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Normalizing constant G_C of one communicating class of a closed
12 * PASS-AND-SWAP (P&S) network, plus one aggregated delay.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_pas_nc.m.
15 *
16 * With a non-empty swap graph the ordered-state chain is reducible (Comte and
17 * Dorsman, 2021, arXiv:2009.12299): the recurrent communicating classes are the
18 * placement-order-adhering sets and the product form pi(c) = prod_m Phi_m(c_m)/G_C
19 * holds per class. This routine returns that per-class constant.
20 *
21 * Method. Build station M's chain head-first: appending class r at chain
22 * position k = |occ|+1 is admissible iff no class already placed at that
23 * station must come after r, and contributes the reciprocal OI prefix rate
24 * 1/mu_M(occ+e_r); the chain may be finalized (recursing to station M-1) only
25 * when occ is a placement-order ideal at full multiplicity. Once every P&S
26 * station is peeled the residual population sits at the delay node with the
27 * multinomial weight prod_r Z_r^{N_r}/N_r!.
28 *
29 * This is a MICROSTATE routine: it walks the ordered chains position by
30 * position, because with a placement order the reachable set is a set of
31 * ORDERINGS that does not collapse onto the count lattice. With an empty order
32 * the node count is sum_{b<=N} C(|b|+M-1,M-1) |b|!/prod_r b_r!, factorial in
33 * the total population -- use pfqn_ncoi for the plain OI case, which returns
34 * the same constant on the count lattice.
35 *
36 * The occupancy shift is carried as an explicit vector per station rather than
37 * by wrapping the callable in a new closure at every level as MATLAB does
38 * (`shifted = @(state) active(state + e_r)`). The two are the same function;
39 * the vector form avoids a closure chain whose depth is the total population.
40 *
41 * Arithmetic: EXACT-CAPABLE, on the same terms as pfqn_ncoi.
42 */
43
44#include <cstddef>
45#include <vector>
46
49#include "line/num/number.h"
50#include "line/util/error.h"
51
52namespace line {
53namespace pfqn {
54
55/**
56 * Placement order of one station: prec[i][j] != 0 iff class i must be placed
57 * before class j. This is the precedence closure of pas_placement, fed by the
58 * global DAG of pas_swap2order.
59 */
60using PlacementOrder = std::vector<std::vector<int>>;
61
62namespace detail {
63
64/**
65 * True iff occ is a placement-order ideal at full multiplicity: for every
66 * i prec j, occ[j] > 0 requires occ[i] == Norig[i]. Reduces to support
67 * downward-closure when Norig is all ones, and to "always true" for an empty
68 * order.
69 */
70inline bool pas_nc_isideal(const std::vector<int>& occ, const PlacementOrder& prec,
71 const std::vector<int>& Norig) {
72 if (prec.empty()) return true;
73 const std::size_t R = occ.size();
74 for (std::size_t i = 0; i < R; ++i)
75 for (std::size_t j = 0; j < R; ++j)
76 if (prec[i][j] != 0 && occ[j] > 0 && occ[i] < Norig[i]) return false;
77 return true;
78}
79
80template <class T>
81T pas_nc_rec(const std::vector<T>& Z, std::vector<int>& N, const std::vector<int>& Norig,
82 const std::vector<OiRate<T>>& mu, const std::vector<PlacementOrder>& prec,
83 std::size_t nsta, std::vector<int>& occ) {
84 const std::size_t R = N.size();
85 const T zero = num_traits<T>::from_int(0);
86 const T one = num_traits<T>::from_int(1);
87
88 if (nsta == 0) {
89 // Only the aggregated delay is left: the multinomial weight.
90 T f = one;
91 for (std::size_t r = 0; r < R; ++r) {
92 if (N[r] == 0) continue;
93 if (!(Z[r] > zero)) return zero; // population with no delay demand
94 f *= num_pow_int(Z[r], static_cast<unsigned>(N[r])) /
95 num_factorial<T>(static_cast<unsigned>(N[r]));
96 }
97 return f;
98 }
99
100 const PlacementOrder& precm = prec[nsta - 1];
101
102 // Step A: finalize this station's chain and peel to the next one, but only
103 // if the accumulated occupancy is a placement-order ideal.
104 T G = zero;
105 if (pas_nc_isideal(occ, precm, Norig)) {
106 std::vector<int> empty(R, 0);
107 G = pas_nc_rec(Z, N, Norig, mu, prec, nsta - 1, empty);
108 }
109
110 // Step B: append one more class r at the next chain position.
111 for (std::size_t r = 0; r < R; ++r) {
112 if (N[r] == 0) continue;
113 bool blocked = false;
114 if (!precm.empty()) {
115 for (std::size_t j = 0; j < R; ++j) {
116 if (occ[j] > 0 && precm[r][j] != 0) {
117 blocked = true;
118 break;
119 }
120 }
121 }
122 if (blocked) continue;
123 occ[r] += 1;
124 const T rate = mu[nsta - 1](occ);
125 if (rate > zero) {
126 N[r] -= 1;
127 G += pas_nc_rec(Z, N, Norig, mu, prec, nsta, occ) / rate;
128 N[r] += 1;
129 }
130 occ[r] -= 1;
131 }
132 return G;
133}
134
135} // namespace detail
136
137/**
138 * @brief Normalizing constant G_C of one communicating class of a closed
139 * PASS-AND-SWAP (P&S) network, plus one aggregated delay.
140 *
141 * @param Z (R) think-time demand of the aggregated delay node
142 * @param N (R) closed population, finite
143 * @param mu (M) P&S rate callables, one per station; may be empty
144 * @param prec (M) placement orders, one per station, each R x R with
145 * prec[m][i][j] != 0 iff class i must be placed before class j at
146 * station m. An empty vector, or an empty matrix for a station,
147 * means no order there, so G is the plain OI constant. NOTE the
148 * orientation: around a cycle each downstream station traverses its
149 * chain in the opposite direction, so downstream stations take the
150 * TRANSPOSE of the upstream order. Passing the same matrix to both
151 * stations of a cycle silently returns a smaller, wrong G.
152 */
153template <class T>
154NcResult<T> pfqn_pas_nc(const std::vector<T>& Z, const std::vector<int>& N,
155 const std::vector<OiRate<T>>& mu,
156 const std::vector<PlacementOrder>& prec) {
157 const std::size_t R = N.size();
158 if (Z.size() != R) throw InputError("pfqn_pas_nc: Z and N must have the same class count");
159 for (int v : N)
160 if (v < 0) throw InputError("pfqn_pas_nc: requires finite, nonnegative populations");
161 for (std::size_t i = 0; i < mu.size(); ++i)
162 if (!mu[i]) throw InputError("pfqn_pas_nc: a P&S rate callable is empty");
163
164 std::vector<PlacementOrder> prel;
165 if (prec.empty()) {
166 prel.assign(mu.size(), PlacementOrder());
167 } else if (prec.size() == 1 && mu.size() > 1) {
168 prel.assign(mu.size(), prec[0]);
169 } else {
170 if (prec.size() != mu.size())
171 throw InputError("pfqn_pas_nc: prec must supply one precedence matrix per station");
172 prel = prec;
173 }
174 for (std::size_t m = 0; m < prel.size(); ++m) {
175 if (prel[m].empty()) continue;
176 if (prel[m].size() != R)
177 throw InputError("pfqn_pas_nc: each precedence matrix must be R x R");
178 for (std::size_t i = 0; i < R; ++i)
179 if (prel[m][i].size() != R)
180 throw InputError("pfqn_pas_nc: each precedence matrix must be R x R");
181 }
182
183 std::vector<int> Nw(N);
184 std::vector<int> occ(R, 0);
185 const T G = detail::pas_nc_rec(Z, Nw, N, mu, prel, mu.size(), occ);
186 return {G, num_traits<T>::log_as_double(G)};
187}
188
189/** Plain OI case (no placement order); prefer pfqn_ncoi, which is cheaper. */
190template <class T>
191NcResult<T> pfqn_pas_nc(const std::vector<T>& Z, const std::vector<int>& N,
192 const std::vector<OiRate<T>>& mu) {
193 return pfqn_pas_nc(Z, N, mu, std::vector<PlacementOrder>());
194}
195
196} // namespace pfqn
197} // namespace line
198
199#endif // LINE_API_PFQN_PAS_NC_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
NcResult< T > pfqn_pas_nc(const std::vector< T > &Z, const std::vector< int > &N, const std::vector< OiRate< T > > &mu, const std::vector< PlacementOrder > &prec)
Normalizing constant G_C of one communicating class of a closed PASS-AND-SWAP (P&S) network,...
std::function< T(const std::vector< int > &)> OiRate
An OI station's total service rate as a function of the occupancy vector.
Definition pfqn_ncoi.h:61
std::vector< std::vector< int > > PlacementOrder
Placement order of one station: prec[i][j] != 0 iff class i must be placed before class j.
Definition pfqn_pas_nc.h:60
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.
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Normalizing constant of a closed network of ORDER-INDEPENDENT (OI) / pass-and-swap stations with empt...
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
Definition pfqn_ca.h:44