LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mdd_ps.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_MDD_MDD_PS_H
6#define LINE_API_MDD_MDD_PS_H
7
8/**
9 * @file
10 * @ingroup api_mdd
11 * Kronecker rate descriptor for shared-server stations with phase-type service.
12 *
13 * Port of matlab/src/api/mdd/mdd_ps.m, jline.api.mdd.Mdd_ps and
14 * python/line_solver/api/mdd/ps.py.
15 *
16 * Under processor sharing every job at a station is in service at once, each
17 * holding its own phase, so naming a single in-service phase (what
18 * `mdd_descriptor` does, which is non-preemptive semantics) cannot represent the
19 * state. The local state here is instead the PER-PHASE COUNT vector
20 * v = (v_1,...,v_h), v_a jobs in phase a, with n = sum(v) jobs present. That is
21 * still a per-station quantity, so every event stays a product of per-level
22 * terms and the Kronecker form of Eq. 1 survives.
23 *
24 * With one server shared by n jobs each job advances at rate 1/n, so from local
25 * state v with n = sum(v):
26 *
27 * internal v -> v - e_a + e_b at v_a * D0[a][b] / n (a != b)
28 * departure v -> v - e_a at v_a * t[a] / n * P[i][j]
29 * arrival v -> v + e_b at pie[b]
30 *
31 * An infinite-server (delay) station is the same without the 1/n scaling. For
32 * h = 1 the departure rate collapses to n*mu/n = mu at PS and to n*mu at IS,
33 * reproducing the usual single-server and delay rate laws.
34 *
35 * The local domain is the number of compositions of 0..N over h phases,
36 * C(N+h,h), against 1+N*h for the non-preemptive encoding: the price of tracking
37 * every job's phase rather than one.
38 */
39
40#include <cmath>
41#include <cstddef>
42#include <map>
43#include <string>
44#include <vector>
45
47#include "line/num/number.h"
48#include "line/util/error.h"
49#include "line/util/matrix.h"
50
51namespace line {
52namespace mdd {
53
54namespace detail {
55
56/** Compositions of 0..N over h phases, in the reference's row order. */
57inline std::vector<std::vector<int>> ps_compositions(std::size_t h, int N) {
58 std::vector<std::vector<int>> out;
59 if (h == 1) {
60 for (int n = 0; n <= N; ++n) out.push_back(std::vector<int>(1, n));
61 return out;
62 }
63 const std::vector<std::vector<int>> sub = ps_compositions(h - 1, N);
64 for (int v1 = 0; v1 <= N; ++v1) {
65 for (std::size_t r = 0; r < sub.size(); ++r) {
66 int s = 0;
67 for (std::size_t a = 0; a < sub[r].size(); ++a) s += sub[r][a];
68 if (s > N - v1) continue;
69 std::vector<int> row;
70 row.push_back(v1);
71 row.insert(row.end(), sub[r].begin(), sub[r].end());
72 out.push_back(row);
73 }
74 }
75 return out;
76}
77
78/** Service-rate scaling: 1/n shared by n jobs at PS, unscaled at IS. */
79template <class T>
80T ps_share(int n, double srv) {
81 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
82 if (n == 0) return zero;
83 if (std::isinf(srv)) return one;
84 return T(one / num_traits<T>::from_int(n));
85}
86
87inline std::size_t ps_lookup(const std::map<std::vector<int>, std::size_t>& lut,
88 const std::vector<int>& v) {
89 const std::map<std::vector<int>, std::size_t>::const_iterator it = lut.find(v);
90 if (it == lut.end())
91 throw InputError("mdd_ps: a per-phase count vector left the composition state space");
92 return it->second;
93}
94
95template <class T>
96MddLocalMatrix<T> ps_internal(const Matrix<T>& D0i, const std::vector<std::vector<int>>& C,
97 const std::map<std::vector<int>, std::size_t>& lut, std::size_t h,
98 std::size_t d, double srv) {
99 const T zero = num_traits<T>::from_int(0);
100 typename MddLocalMatrix<T>::Builder bld(d);
101 for (std::size_t r = 0; r < d; ++r) {
102 const std::vector<int>& v = C[r];
103 int n = 0;
104 for (std::size_t a = 0; a < h; ++a) n += v[a];
105 if (n == 0) continue;
106 const T sc = ps_share<T>(n, srv);
107 for (std::size_t a = 0; a < h; ++a) {
108 if (v[a] == 0) continue;
109 for (std::size_t b = 0; b < h; ++b) {
110 if (a == b || D0i(a, b) == zero) continue;
111 std::vector<int> w = v;
112 --w[a];
113 ++w[b];
114 bld.add(r, ps_lookup(lut, w),
115 T(num_traits<T>::from_int(v[a]) * D0i(a, b) * sc));
116 }
117 }
118 }
119 return bld.build();
120}
121
122template <class T>
123MddLocalMatrix<T> ps_departure(const Matrix<T>& D1i, const std::vector<std::vector<int>>& C,
124 const std::map<std::vector<int>, std::size_t>& lut, std::size_t h,
125 std::size_t d, double srv, const T& pr) {
126 const T zero = num_traits<T>::from_int(0);
127 std::vector<T> t(h, zero);
128 for (std::size_t a = 0; a < h; ++a) {
129 T s = zero;
130 for (std::size_t b = 0; b < h; ++b) s += D1i(a, b);
131 t[a] = s;
132 }
133 typename MddLocalMatrix<T>::Builder bld(d);
134 for (std::size_t r = 0; r < d; ++r) {
135 const std::vector<int>& v = C[r];
136 int n = 0;
137 for (std::size_t a = 0; a < h; ++a) n += v[a];
138 if (n == 0) continue;
139 const T sc = ps_share<T>(n, srv);
140 for (std::size_t a = 0; a < h; ++a) {
141 if (v[a] == 0 || t[a] == zero) continue;
142 std::vector<int> w = v;
143 --w[a];
144 bld.add(r, ps_lookup(lut, w),
145 T(num_traits<T>::from_int(v[a]) * t[a] * sc * pr));
146 }
147 }
148 return bld.build();
149}
150
151template <class T>
152MddLocalMatrix<T> ps_arrival(const std::vector<T>& pieb, const std::vector<std::vector<int>>& C,
153 const std::map<std::vector<int>, std::size_t>& lut, std::size_t h,
154 int N, std::size_t d) {
155 const T zero = num_traits<T>::from_int(0);
156 typename MddLocalMatrix<T>::Builder bld(d);
157 for (std::size_t r = 0; r < d; ++r) {
158 const std::vector<int>& v = C[r];
159 int n = 0;
160 for (std::size_t a = 0; a < h; ++a) n += v[a];
161 if (n >= N) continue;
162 for (std::size_t b = 0; b < h; ++b) {
163 if (pieb[b] == zero) continue;
164 std::vector<int> w = v;
165 ++w[b];
166 bld.add(r, ps_lookup(lut, w), pieb[b]);
167 }
168 }
169 return bld.build();
170}
171
172template <class T>
173std::vector<std::vector<int>> ps_successors(
174 const std::vector<int>& s, const std::vector<Matrix<T>>& D0, const std::vector<Matrix<T>>& D1,
175 const std::vector<std::vector<T>>& pie, const std::vector<std::size_t>& h,
176 const std::vector<std::vector<std::vector<int>>>& comp,
177 const std::vector<std::map<std::vector<int>, std::size_t>>& lut, int N, const Matrix<T>& P) {
178 const T zero = num_traits<T>::from_int(0);
179 const std::size_t K = s.size();
180 std::vector<std::vector<int>> out;
181 for (std::size_t i = 0; i < K; ++i) {
182 const std::vector<int>& v = comp[i][static_cast<std::size_t>(s[i])];
183 int n = 0;
184 for (std::size_t a = 0; a < h[i]; ++a) n += v[a];
185 if (n == 0) continue;
186 // internal phase moves
187 for (std::size_t a = 0; a < h[i]; ++a) {
188 if (v[a] == 0) continue;
189 for (std::size_t b = 0; b < h[i]; ++b) {
190 if (a == b || D0[i](a, b) == zero) continue;
191 std::vector<int> w = v;
192 --w[a];
193 ++w[b];
194 std::vector<int> t = s;
195 t[i] = static_cast<int>(ps_lookup(lut[i], w));
196 out.push_back(t);
197 }
198 }
199 // completions routed to j
200 for (std::size_t a = 0; a < h[i]; ++a) {
201 if (v[a] == 0) continue;
202 T ta = zero;
203 for (std::size_t b = 0; b < h[i]; ++b) ta += D1[i](a, b);
204 if (ta == zero) continue;
205 std::vector<int> w = v;
206 --w[a];
207 for (std::size_t j = 0; j < K; ++j) {
208 if (j == i || !(P(i, j) > zero)) continue;
209 const std::vector<int>& vj = comp[j][static_cast<std::size_t>(s[j])];
210 int nj = 0;
211 for (std::size_t b = 0; b < h[j]; ++b) nj += vj[b];
212 if (nj >= N) continue;
213 for (std::size_t b = 0; b < h[j]; ++b) {
214 if (pie[j][b] == zero) continue;
215 std::vector<int> wj = vj;
216 ++wj[b];
217 std::vector<int> t = s;
218 t[i] = static_cast<int>(ps_lookup(lut[i], w));
219 t[j] = static_cast<int>(ps_lookup(lut[j], wj));
220 out.push_back(t);
221 }
222 }
223 }
224 }
225 return out;
226}
227
228} // namespace detail
229
230/**
231 * Build the descriptor.
232 *
233 * @param mu station service rates, ignored where proc gives a law
234 * @param P station-to-station routing matrix, row-stochastic
235 * @param servers servers per station, 1 (PS) or infinite (IS); no other value
236 * has a per-phase-count encoding here
237 * @param N closed population
238 * @param proc per-station service law; an absent or `present == false` entry is
239 * an exponential station
240 */
241template <class T>
242MddDescriptor<T> mdd_ps(const std::vector<T>& mu, const Matrix<T>& P,
243 const std::vector<double>& servers, int N,
244 const std::vector<MddServiceLaw<T>>& proc =
245 std::vector<MddServiceLaw<T>>()) {
246 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
247 const std::size_t K = mu.size();
248 if (K == 0) throw InputError("mdd_ps: the network has no stations");
249 if (P.rows() != K || P.cols() != K)
250 throw InputError("mdd_ps: the routing matrix is not (K x K)");
251 if (servers.size() != K) throw InputError("mdd_ps: one server count per station is required");
252
253 std::vector<Matrix<T>> D0(K), D1(K);
254 std::vector<std::vector<T>> entry(K);
255 std::vector<std::size_t> h(K, 1);
256
257 for (std::size_t i = 0; i < K; ++i) {
258 if (!(servers[i] == 1 || std::isinf(servers[i])))
259 throw InputError("mdd_ps: station " + std::to_string(i + 1) + " has " +
260 std::to_string(servers[i]) +
261 " servers; only processor sharing (1) and infinite server have a "
262 "per-phase-count encoding here");
263 const bool has_law = i < proc.size() && proc[i].present;
264 if (!has_law) {
265 D0[i] = Matrix<T>(1, 1, T(-mu[i]));
266 D1[i] = Matrix<T>(1, 1, mu[i]);
267 entry[i] = std::vector<T>(1, one);
268 h[i] = 1;
269 continue;
270 }
271 D0[i] = proc[i].D0;
272 D1[i] = proc[i].D1;
273 h[i] = proc[i].phases();
274 entry[i] = mdd_entry_law(proc[i].pie, D1[i], h[i], i, "mdd_ps");
275 }
276
277 // ---- per-station composition state space
278 std::vector<std::vector<std::vector<int>>> comp(K);
279 std::vector<std::map<std::vector<int>, std::size_t>> lut(K);
280 std::vector<int> d(K, 0);
281 for (std::size_t i = 0; i < K; ++i) {
282 comp[i] = detail::ps_compositions(h[i], N);
283 for (std::size_t r = 0; r < comp[i].size(); ++r) lut[i][comp[i][r]] = r;
284 d[i] = static_cast<int>(comp[i].size());
285 }
286
287 MddDescriptor<T> desc;
288 desc.K = K;
289 desc.N = N;
290 desc.domain = d;
291 desc.mu = mu;
292 desc.servers = servers;
293 desc.P = P;
294 desc.nphases = h;
295 desc.valuemap.assign(K, std::vector<double>());
296 for (std::size_t i = 0; i < K; ++i) {
297 desc.valuemap[i].assign(static_cast<std::size_t>(d[i]), 0.0);
298 for (std::size_t r = 0; r < static_cast<std::size_t>(d[i]); ++r) {
299 int s = 0;
300 for (std::size_t a = 0; a < h[i]; ++a) s += comp[i][r][a];
301 desc.valuemap[i][r] = static_cast<double>(s);
302 }
303 }
304
305 // ---- initial state: all jobs at station 1, entered in its entry phase
306 desc.init.assign(K, 0);
307 for (std::size_t i = 0; i < K; ++i) {
308 std::vector<int> v(h[i], 0);
309 if (i == 0) {
310 std::size_t first = 0;
311 for (std::size_t a = 0; a < h[0]; ++a)
312 if (entry[0][a] > zero) {
313 first = a;
314 break;
315 }
316 v[first] = N;
317 }
318 desc.init[i] = static_cast<int>(detail::ps_lookup(lut[i], v));
319 }
320
321 const std::vector<Matrix<T>> fD0 = D0, fD1 = D1;
322 const std::vector<std::vector<T>> fpie = entry;
323 const std::vector<std::size_t> fh = h;
324 const std::vector<std::vector<std::vector<int>>> fcomp = comp;
325 const std::vector<std::map<std::vector<int>, std::size_t>> flut = lut;
326 const Matrix<T> fP = P;
327 const int fN = N;
328 desc.nextfun = [fD0, fD1, fpie, fh, fcomp, flut, fN, fP](const std::vector<int>& state) {
329 return detail::ps_successors(state, fD0, fD1, fpie, fh, fcomp, flut, fN, fP);
330 };
331
332 // ---- events
333 for (std::size_t i = 0; i < K; ++i) {
334 if (h[i] == 1) continue;
335 const MddLocalMatrix<T> Wi = detail::ps_internal(D0[i], comp[i], lut[i], h[i],
336 static_cast<std::size_t>(d[i]),
337 servers[i]);
338 if (Wi.nnz == 0) continue;
339 MddEvent<T> ev;
340 ev.a = i;
341 ev.b = i;
342 ev.lev.push_back(i);
343 ev.W.push_back(Wi);
344 desc.events.push_back(ev);
345 }
346 for (std::size_t a = 0; a < K; ++a)
347 for (std::size_t b = 0; b < K; ++b) {
348 if (a == b || !(P(a, b) > zero)) continue;
349 MddEvent<T> ev;
350 ev.a = a;
351 ev.b = b;
352 ev.lev.push_back(a);
353 ev.lev.push_back(b);
354 ev.W.push_back(detail::ps_departure(D1[a], comp[a], lut[a], h[a],
355 static_cast<std::size_t>(d[a]), servers[a],
356 P(a, b)));
357 ev.W.push_back(detail::ps_arrival(entry[b], comp[b], lut[b], h[b], N,
358 static_cast<std::size_t>(d[b])));
359 desc.events.push_back(ev);
360 }
361 return desc;
362}
363
364} // namespace mdd
365} // namespace line
366
367#endif // LINE_API_MDD_MDD_PS_H
InputError(const std::string &what)
Definition error.h:39
std::size_t size() const
Definition matrix.h:91
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
The rate side of the decision-diagram domain: local matrices, events, the Kronecker descriptor,...
std::vector< T > mdd_entry_law(const std::vector< T > &given, const Matrix< T > &D1, std::size_t h, std::size_t i, const std::string &caller)
Entry law of a phase-type station, taken as given or derived from D1.
Definition mdd_types.h:268
MddDescriptor< T > mdd_ps(const std::vector< T > &mu, const Matrix< T > &P, const std::vector< double > &servers, int N, const std::vector< MddServiceLaw< T > > &proc=std::vector< MddServiceLaw< T > >())
Build the descriptor.
Definition mdd_ps.h:242
Number-type abstraction for the templated API port.
Kronecker rate descriptor of a structured model, the input of mdd_mcd.
Definition mdd_types.h:165
MddNextState nextfun
Successor function over local indices.
Definition mdd_types.h:191
std::vector< int > domain
Local domain per level.
Definition mdd_types.h:171
std::vector< std::vector< double > > valuemap
valuemap[i][idx] is the physical occupancy of level i in local state idx.
Definition mdd_types.h:187
std::vector< double > servers
Servers per station; infinite for a delay station.
Definition mdd_types.h:175
std::vector< std::size_t > nphases
Phases per station, 1 when exponential.
Definition mdd_types.h:179
int N
Closed population; the conservation law the level marginals must satisfy.
Definition mdd_types.h:169
std::vector< T > mu
Station service rates, 1/E[S]; empty for a descriptor with no queueing parameters.
Definition mdd_types.h:173
std::vector< int > init
Initial local index per level.
Definition mdd_types.h:189
std::vector< MddEvent< T > > events
The events of the descriptor.
Definition mdd_types.h:193
std::size_t K
Number of levels, i.e.
Definition mdd_types.h:167
Matrix< T > P
Station-to-station routing matrix.
Definition mdd_types.h:177
One event of the Kronecker rate descriptor.
Definition mdd_types.h:124
std::size_t b
Station (or mode) the event arrives at, 0-based; equals a for an internal event.
Definition mdd_types.h:128
std::size_t a
Station (or transition node) the event departs from, 0-based.
Definition mdd_types.h:126
std::vector< MddLocalMatrix< T > > W
Local matrices at the levels named by lev.
Definition mdd_types.h:132
std::vector< std::size_t > lev
Levels the event touches, as 0-based level indices, aligned with W.
Definition mdd_types.h:130
A local rate matrix W_k^e of the Kronecker descriptor, held row-compressed.
Definition mdd_types.h:46
std::size_t nnz
Total number of stored nonzeros.
Definition mdd_types.h:56
Phase-type service law of one station, as a Markovian (D0,D1) pair.
Definition mdd_types.h:144