LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_chain.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_MVA_SN_CHAIN_H
6#define LINE_SOLVERS_MVA_SN_CHAIN_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Chain aggregation and de-aggregation.
12 *
13 * Ports of matlab/src/api/sn/sn_get_demands_chain.m,
14 * sn_get_product_form_chain_params.m and sn_deaggregate_chain_results.m. These
15 * are the bridge between the class-level layer struct and the chain-level
16 * product-form solvers: a chain is a set of classes a job moves between while
17 * circulating, so the solvers work on chains and the results are pushed back to
18 * classes with the visit-share weights `alpha`.
19 *
20 * NON-FINITE VALUES. MATLAB reaches this code with NaN and Inf in `sn.rates`
21 * (NaN for a station-class pair the class never visits, Inf for an immediate
22 * service) and clears them afterwards with `X(~isfinite(X)) = 0`. The exact
23 * backend has neither value, so the two are carried as explicit flags -- the
24 * layer's `disabled` mask, and a local `st_inf` mask for a zero rate -- and the
25 * same entries end up zero. Every place a MATLAB `isfinite` guard fires, the
26 * corresponding flag test fires here.
27 */
28
29#include <algorithm>
30#include <cmath>
31#include <vector>
32
34#include "line/num/number.h"
35#include "line/util/error.h"
36#include "line/util/matrix.h"
37
38namespace line {
39namespace mva {
40
41using lang::GlobalConstants;
43
44/** The chain-level view of a layer, as sn_get_demands_chain returns it. */
45template <class T>
47 Matrix<T> Lchain; ///< (M x C) demand
48 Matrix<T> STchain; ///< (M x C) mean service time
49 Matrix<T> Vchain; ///< (M x C) visits
50 Matrix<T> alpha; ///< (M x K) class share of its chain's visits at a station
51 std::vector<double> Nchain; ///< (C) population, infinite for an open chain
52 Matrix<T> SCVchain; ///< (M x C)
53 std::vector<std::size_t> refstatchain; ///< (C) 1-based reference station
54 Matrix<T> ST; ///< (M x K) class-level mean service time, 0 where disabled
55};
56
57/**
58 * Port of sn_get_demands_chain.
59 *
60 * @param L the refreshed layer
61 */
62template <class T>
64 const std::size_t M = L.nstations, K = L.nclasses, C = L.nchains;
65 const T zero = num_traits<T>::from_int(0);
66 const T one = num_traits<T>::from_int(1);
67
69 d.ST = Matrix<T>(M, K, zero);
70 std::vector<std::vector<bool>> st_inf(M, std::vector<bool>(K, false));
71 Matrix<T> SCV(M, K, one);
72 std::vector<std::vector<bool>> scv_known(M, std::vector<bool>(K, true));
73 for (std::size_t i = 0; i < M; ++i)
74 for (std::size_t r = 0; r < K; ++r) {
75 if (L.disabled[i][r]) {
76 // MATLAB: ST = 1./rates gives NaN, then ST(isnan(ST)) = 0;
77 // SCV(isnan(SCV)) = 1
78 d.ST(i, r) = zero;
79 SCV(i, r) = one;
80 continue;
81 }
82 if (L.rates(i, r) == zero) {
83 // 1/0 = Inf in MATLAB; every use is guarded by isfinite and
84 // ends up zeroed, so the flag records it and the value stays 0
85 st_inf[i][r] = true;
86 d.ST(i, r) = zero;
87 } else {
88 d.ST(i, r) = T(one / L.rates(i, r));
89 }
90 SCV(i, r) = L.scv(i, r);
91 // MATLAB: alphachain sums alpha over the classes whose SCV isfinite
92 scv_known[i][r] = std::isfinite(num_traits<T>::to_double(SCV(i, r)));
93 }
94
95 d.alpha = Matrix<T>(M, K, zero);
96 d.Vchain = Matrix<T>(M, C, zero);
97 std::vector<std::vector<bool>> v_bad(M, std::vector<bool>(C, false));
98
99 for (std::size_t c = 0; c < C; ++c) {
100 const std::vector<std::size_t>& ic = L.inchain[c];
101 const std::size_t rstat = L.classes[ic[0] - 1].refstat;
102 // denominator: the reference class's visits when the chain has one
103 // AND that class visits the reference station (MATLAB's useRefClass:
104 // a ref class that never visits it gives a zero denominator, which
105 // would turn every Vchain into Inf and then into 0), otherwise the
106 // whole chain's visits at the reference station
107 T den = zero;
108 const std::size_t rsf = L.stateful_of_station(rstat) - 1;
109 const bool use_ref_class =
110 L.refclass[c] > 0 &&
112 if (use_ref_class) {
113 den = L.visits[c](rsf, L.refclass[c] - 1);
114 } else {
115 for (std::size_t k : ic) den += L.visits[c](rsf, k - 1);
116 }
117 for (std::size_t i = 0; i < M; ++i) {
118 const std::size_t sf = L.stateful_of_station(i + 1) - 1;
119 T num = zero;
120 for (std::size_t k : ic) num += L.visits[c](sf, k - 1);
121 if (den == zero)
122 v_bad[i][c] = true; // MATLAB: 0/0 or x/0 -> NaN or Inf, then zeroed
123 else
124 d.Vchain(i, c) = T(num / den);
125 if (num != zero)
126 for (std::size_t k : ic)
127 d.alpha(i, k - 1) = T(d.alpha(i, k - 1) + L.visits[c](sf, k - 1) / num);
128 }
129 }
130 for (std::size_t i = 0; i < M; ++i)
131 for (std::size_t c = 0; c < C; ++c)
132 if (v_bad[i][c]) d.Vchain(i, c) = zero;
133
134 // renormalise each chain's visits by the value at its reference station
135 for (std::size_t c = 0; c < C; ++c) {
136 const std::size_t rstat = L.classes[L.inchain[c][0] - 1].refstat;
137 const T vref = d.Vchain(rstat - 1, c);
138 // MATLAB: `if abs(vref) > GlobalConstants.Zero`, else the column is left as is
140 for (std::size_t i = 0; i < M; ++i) d.Vchain(i, c) = T(d.Vchain(i, c) / vref);
141 }
142 for (std::size_t i = 0; i < M; ++i)
143 for (std::size_t k = 0; k < K; ++k)
145 d.alpha(i, k) = zero;
146
147 d.Lchain = Matrix<T>(M, C, zero);
148 d.STchain = Matrix<T>(M, C, zero);
149 d.SCVchain = Matrix<T>(M, C, zero);
150 d.Nchain.assign(C, 0.0);
151 d.refstatchain.assign(C, 1);
152
153 for (std::size_t c = 0; c < C; ++c) {
154 const std::vector<std::size_t>& ic = L.inchain[c];
155 bool open = false;
156 double n = 0.0;
157 for (std::size_t k : ic) {
158 const double p = L.classes[k - 1].population;
159 if (std::isinf(p)) open = true;
160 n += p;
161 }
162 d.Nchain[c] = open ? std::numeric_limits<double>::infinity() : n;
163 const std::size_t rstat = L.classes[ic[0] - 1].refstat;
164 d.refstatchain[c] = rstat;
165
166 for (std::size_t i = 0; i < M; ++i) {
167 bool bad = false;
168 T st = zero;
169 for (std::size_t k : ic) {
170 // MATLAB: ST(i,inchain) * alpha(i,inchain)' with an Inf entry is
171 // Inf or NaN (Inf * 0) either way, and STchain(~isfinite) = 0
172 if (st_inf[i][k - 1]) bad = true;
173 st += d.ST(i, k - 1) * d.alpha(i, k - 1);
174 }
175 if (open && i + 1 == rstat) {
176 // a source station: the chain service time is 1 / total arrival rate
177 T lam = zero;
178 for (std::size_t k : ic)
179 if (!L.disabled[i][k - 1]) lam += L.rates(i, k - 1);
180 // MATLAB overwrites the dot product here, so only a zero total
181 // rate (1/0 = Inf, then cleared) makes this entry bad
182 bad = (lam == zero);
183 if (!bad) st = T(num_traits<T>::from_int(1) / lam);
184 }
185 d.STchain(i, c) = bad ? zero : st;
186 d.Lchain(i, c) = T(d.Vchain(i, c) * d.STchain(i, c));
187
188 // MATLAB: alphachain = sum(alpha(i, inchain(isfinite(SCV(i, inchain)))))
189 T ach = zero;
190 for (std::size_t k : ic)
191 if (scv_known[i][k - 1]) ach += d.alpha(i, k - 1);
192 if (ach > zero) {
193 T s = zero;
194 for (std::size_t k : ic) s += SCV(i, k - 1) * d.alpha(i, k - 1);
195 d.SCVchain(i, c) = T(s / ach);
196 }
197 }
198 }
199 return d;
200}
201
202/** Class-level results, as sn_deaggregate_chain_results returns them. */
203template <class T>
206 std::vector<T> C, X;
207};
208
209/**
210 * Port of sn_deaggregate_chain_results.
211 *
212 * OPEN CHAINS. A closed class's share of its chain's throughput is its share of
213 * the visits at the reference station; an open class's is its share of the
214 * DEPARTURES, i.e. the chain's visit count at the Sink, because an open job
215 * completes by leaving rather than by returning. Only X and C are computed this
216 * way; Q, U, R and T follow the same path for both.
217 *
218 * The Sink visits come from this layer's own `nodevisits`, normalised by the
219 * chain's reference NODE. MATLAB normalises the node-level visits by
220 * `statefulToNode(refstat(...))`, which feeds a STATION index to a STATEFUL ->
221 * node map and lands on an unrelated node, so its Sink visits carry an
222 * arbitrary scale factor (3 rather than 1 on the fork layer of lqn_workflows).
223 * The correct normalisation is used here. It reaches X and C only, which
224 * SolverLN does not read back, so no layered metric depends on the choice.
225 */
226template <class T>
228 const Matrix<T>& Qchain, const Matrix<T>& Uchain,
229 const Matrix<T>& Rchain, const Matrix<T>& Tchain,
230 const std::vector<T>& Xchain) {
231 const std::size_t M = L.nstations, K = L.nclasses;
232 const T zero = num_traits<T>::from_int(0);
234 r.Q = Matrix<T>(M, K, zero);
235 r.U = Matrix<T>(M, K, zero);
236 r.R = Matrix<T>(M, K, zero);
237 r.Tp = Matrix<T>(M, K, zero);
238 r.C.assign(K, zero);
239 r.X.assign(K, zero);
240
241 std::vector<T> Vsink(K, zero);
242 if (L.sinkNode > 0)
243 for (std::size_t c = 0; c < L.nchains; ++c)
244 for (std::size_t k = 0; k < K; ++k) Vsink[k] += L.nodevisits[c](L.sinkNode - 1, k);
245
246 for (std::size_t c = 0; c < L.nchains; ++c) {
247 const std::vector<std::size_t>& ic = L.inchain[c];
248 const bool open = std::isinf(d.Nchain[c]);
249 if (open && L.sinkNode == 0)
250 throw UnsupportedError(ErrorKind{"Topology"},
251 "sn_deaggregate_chain_results: an open chain needs the Sink node visits, and this "
252 "layer has no Sink node to read them from");
253 for (std::size_t kk : ic) {
254 const std::size_t k = kk - 1;
255 const std::size_t rstat = L.classes[k].refstat;
256 r.X[k] = open ? T(Xchain[c] * Vsink[k]) : T(Xchain[c] * d.alpha(rstat - 1, k));
257 const T vref = d.Vchain(rstat - 1, c);
258 for (std::size_t i = 0; i < M; ++i) {
259 const double S = L.stations[i].nservers;
260 const T share = (d.STchain(i, c) > zero)
261 ? T(d.alpha(i, k) * d.ST(i, k) / d.STchain(i, c))
262 : d.alpha(i, k);
263 if (vref != zero) {
264 const T base = T(d.ST(i, k) * (Xchain[c] * d.Vchain(i, c) / vref) *
265 d.alpha(i, k));
266 if (std::isinf(S)) {
267 r.U(i, k) = base;
268 } else if (Uchain.rows() == 0) {
269 r.U(i, k) = T(base / num_traits<T>::from_double(S));
270 } else {
271 // busy time splits by the class's share of the chain DEMAND
272 // (alpha*ST/STchain), an identity; alpha alone held only for
273 // equal service times (sn_deaggregate_chain_results.m)
274 r.U(i, k) = T(Uchain(i, c) * share);
275 }
276 }
277 if (d.Lchain(i, c) > zero) {
278 if (Qchain.rows() > 0) {
279 // within a product-form chain the classes at a station split
280 // the chain's jobs by demand, not by visits
281 r.Q(i, k) = T(Qchain(i, c) * share);
282 } else if (d.STchain(i, c) != zero && vref != zero) {
283 r.Q(i, k) = T(Rchain(i, c) * d.ST(i, k) / d.STchain(i, c) * Xchain[c] *
284 d.Vchain(i, c) / vref * d.alpha(i, k));
285 }
286 r.Tp(i, k) = T(Tchain(i, c) * d.alpha(i, k));
287 r.R(i, k) = r.Tp(i, k) == zero ? zero : T(r.Q(i, k) / r.Tp(i, k));
288 }
289 }
290 const double njobs = L.classes[k].population;
291 r.C[k] = r.X[k] == zero ? zero
292 : T(num_traits<T>::from_double(njobs) / r.X[k]);
293 }
294 }
295 auto absify = [&](Matrix<T>& A) {
296 for (std::size_t i = 0; i < A.rows(); ++i)
297 for (std::size_t j = 0; j < A.cols(); ++j)
298 if (A(i, j) < zero) A(i, j) = T(-A(i, j));
299 };
300 absify(r.Q);
301 absify(r.U);
302 absify(r.R);
303 absify(r.Tp);
304 for (T& x : r.C)
305 if (x < zero) x = T(-x);
306 for (T& x : r.X)
307 if (x < zero) x = T(-x);
308 return r;
309}
310
311/** Product-form chain parameters, the split into queueing and delay stations. */
312template <class T>
314 Matrix<T> D; ///< (Mq x C) demands at queueing stations
315 Matrix<T> Z; ///< (Md x C) demands at delay stations
316 std::vector<T> lambda; ///< (C) chain arrival rates, zero on a closed chain
317 std::vector<double> N; ///< (C) populations
318 std::vector<double> S; ///< (Mq) server counts
319 std::vector<std::size_t> queue_stations; ///< 1-based
320 std::vector<std::size_t> delay_stations; ///< 1-based
321 /**
322 * The reference's 5th output, `mu` of sn_get_product_form_params:
323 * (Mq x ceil(sum finite N) + max S) with mu(i, n) = min(n, nservers_i).
324 */
326 /**
327 * The reference's 7th output: `Vchain` with the Source and Join station
328 * rows removed, (M - #Source - #Join) x C.
329 */
331};
332
333/**
334 * Port of sn_get_product_form_chain_params.
335 *
336 * `lambda` is the chain-level arrival rate the reference builds by summing the class
337 * rates of `sn_get_product_form_params` over `sn.inchain{c}`: the Source's rate for an
338 * open class, nothing for a closed one. Only the mixed `lin` branch of solver_amva
339 * reads it, so a closed model leaves the vector at zero.
340 */
341template <class T>
344 for (std::size_t i = 0; i < L.nstations; ++i) {
345 if (L.stations[i].nodetype == qn::NodeType::Queue) p.queue_stations.push_back(i + 1);
346 else if (L.stations[i].nodetype == qn::NodeType::Delay) p.delay_stations.push_back(i + 1);
347 }
348 const T zero = num_traits<T>::from_int(0);
349 p.D = Matrix<T>(p.queue_stations.size(), L.nchains, zero);
350 p.Z = Matrix<T>(p.delay_stations.size(), L.nchains, zero);
351 for (std::size_t c = 0; c < L.nchains; ++c) {
352 for (std::size_t a = 0; a < p.queue_stations.size(); ++a)
353 p.D(a, c) = d.Lchain(p.queue_stations[a] - 1, c);
354 for (std::size_t a = 0; a < p.delay_stations.size(); ++a)
355 p.Z(a, c) = d.Lchain(p.delay_stations[a] - 1, c);
356 }
357 // MATLAB: `if isempty(Z), Z = 0*N; end`, a 1 x C row of zeros
358 if (p.delay_stations.empty()) p.Z = Matrix<T>(1, L.nchains, zero);
359 p.N = d.Nchain;
360
361 // The reference's `V`: Vchain with the Source and Join rows dropped.
362 {
363 std::vector<std::size_t> keep;
364 for (std::size_t i = 0; i < L.nstations; ++i)
365 if (L.stations[i].nodetype != qn::NodeType::Source &&
366 L.stations[i].nodetype != qn::NodeType::Join)
367 keep.push_back(i);
368 p.V = Matrix<T>(keep.size(), L.nchains, zero);
369 for (std::size_t a = 0; a < keep.size(); ++a)
370 for (std::size_t c = 0; c < L.nchains; ++c) p.V(a, c) = d.Vchain(keep[a], c);
371 }
372
373 // An open class contributes the Source's rate for it, a closed one nothing. A
374 // disabled pair contributes nothing either, which is where MATLAB's 'omitnan' sum
375 // lands: sn.rates is NaN there.
376 std::size_t source_station = 0;
377 for (std::size_t i = 0; i < L.nstations; ++i)
378 if (L.stations[i].nodetype == qn::NodeType::Source) source_station = i + 1;
379 p.lambda.assign(L.nchains, zero);
380 if (source_station > 0)
381 for (std::size_t c = 0; c < L.nchains; ++c)
382 for (std::size_t r = 0; r < L.nclasses; ++r) {
383 if (!L.chains[c][r]) continue;
384 if (!std::isinf(L.classes[r].population)) continue;
385 if (L.disabled[source_station - 1][r]) continue;
386 p.lambda[c] += L.rates(source_station - 1, r);
387 }
388
389 for (std::size_t a = 0; a < p.queue_stations.size(); ++a)
390 p.S.push_back(L.stations[p.queue_stations[a] - 1].nservers);
391
392 // `mu` of sn_get_product_form_params: (Mq x ceil(Nct) + max finite S), with
393 // mu(ist, n) = min(n, nservers_ist); Nct is the closed population.
394 {
395 double nct = 0.0;
396 for (std::size_t r = 0; r < L.nclasses; ++r)
397 if (std::isfinite(L.classes[r].population)) nct += L.classes[r].population;
398 double maxS = 0.0;
399 for (std::size_t a = 0; a < p.S.size(); ++a)
400 if (std::isfinite(p.S[a]) && p.S[a] > maxS) maxS = p.S[a];
401 const std::size_t width = static_cast<std::size_t>(std::ceil(nct)) +
402 static_cast<std::size_t>(maxS);
403 p.mu = Matrix<T>(p.queue_stations.size(), width, num_traits<T>::from_int(1));
404 for (std::size_t a = 0; a < p.queue_stations.size(); ++a)
405 for (std::size_t n = 1; n <= width; ++n)
406 p.mu(a, n - 1) = num_traits<T>::from_double(
407 std::min<double>(static_cast<double>(n), p.S[a]));
408 }
409 return p;
410}
411
412/**
413 * Aggregate a class-indexed interlock matrix to the chain basis the MVA analyzers
414 * work in. IL[r][s] is the share of the class-s queue that a class-r arrival must
415 * not see, the interlocked flow of Franks (1999), Eq. (4.7). Two classes of the same
416 * chain belong to the same client, so the diagonal blocks carry no information and
417 * the chain diagonal stays zero: an arrival always sees its own chain in full.
418 * Returns an empty matrix when nothing is interlocked.
419 */
420template <class T>
422 const std::vector<std::vector<double>>& ILclass) {
423 if (ILclass.empty()) return Matrix<T>();
424 const std::size_t R = L.nclasses, K = L.nchains;
425 if (ILclass.size() != R)
426 throw InputError("sn_interlock_chain: the interlock matrix disagrees with the class count");
427 const T zero = num_traits<T>::from_int(0);
428 Matrix<T> ILchain(K, K, zero);
429 bool any = false;
430 for (std::size_t cr = 0; cr < K; ++cr)
431 for (std::size_t cs = 0; cs < K; ++cs) {
432 if (cr == cs) continue;
433 double best = 0.0;
434 for (std::size_t r = 0; r < R; ++r) {
435 if (!L.chains[cr][r]) continue;
436 if (ILclass[r].size() != R)
437 throw InputError("sn_interlock_chain: the interlock matrix is not square");
438 for (std::size_t sIl = 0; sIl < R; ++sIl) {
439 if (!L.chains[cs][sIl]) continue;
440 if (ILclass[r][sIl] > best) best = ILclass[r][sIl];
441 }
442 }
443 if (best > 0.0) {
444 ILchain(cr, cs) = num_traits<T>::from_double(best);
445 any = true;
446 }
447 }
448 return any ? ILchain : Matrix<T>();
449}
450
451} // namespace mva
452} // namespace line
453
454#endif // LINE_SOLVERS_MVA_SN_CHAIN_H
InputError(const std::string &what)
Definition error.h:105
std::size_t rows() const
Definition matrix.h:89
UnsupportedError(const std::string &what)
Definition error.h:119
A network plus its refreshed NetworkStruct.
std::vector< Matrix< T > > nodevisits
(nchains) each (nnodes x nclasses)
std::size_t stateful_of_station(std::size_t st) const
std::vector< std::vector< bool > > chains
(nchains x nclasses)
std::vector< std::size_t > refclass
(nchains) 1-based class, 0 = none
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::size_t sinkNode
1-based NODE index of the Sink, 0 = none (it is not a station)
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
std::vector< Matrix< T > > visits
(nchains) each (nstateful x nclasses)
The exception types the port throws.
Dense matrix and non-owning view.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:185
PfChainParams< T > sn_get_product_form_chain_params(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d)
Port of sn_get_product_form_chain_params.
Definition sn_chain.h:342
Matrix< T > sn_interlock_chain(const qn::NetworkStruct< T > &L, const std::vector< std::vector< double > > &ILclass)
Aggregate a class-indexed interlock matrix to the chain basis the MVA analyzers work in.
Definition sn_chain.h:421
ClassResults< T > sn_deaggregate_chain_results(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const Matrix< T > &Qchain, const Matrix< T > &Uchain, const Matrix< T > &Rchain, const Matrix< T > &Tchain, const std::vector< T > &Xchain)
Port of sn_deaggregate_chain_results.
Definition sn_chain.h:227
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
Definition sn_chain.h:63
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_abs(const T &v)
Definition number.h:184
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The chain-level view of a layer, as sn_get_demands_chain returns it.
Definition sn_chain.h:46
Matrix< T > ST
(M x K) class-level mean service time, 0 where disabled
Definition sn_chain.h:54
std::vector< std::size_t > refstatchain
(C) 1-based reference station
Definition sn_chain.h:53
std::vector< double > Nchain
(C) population, infinite for an open chain
Definition sn_chain.h:51
Matrix< T > alpha
(M x K) class share of its chain's visits at a station
Definition sn_chain.h:50
Matrix< T > STchain
(M x C) mean service time
Definition sn_chain.h:48
Matrix< T > Lchain
(M x C) demand
Definition sn_chain.h:47
Matrix< T > Vchain
(M x C) visits
Definition sn_chain.h:49
Matrix< T > SCVchain
(M x C)
Definition sn_chain.h:52
Class-level results, as sn_deaggregate_chain_results returns them.
Definition sn_chain.h:204
std::vector< T > C
Definition sn_chain.h:206
std::vector< T > X
Definition sn_chain.h:206
static constexpr double Zero
Definition lang_types.h:767
Product-form chain parameters, the split into queueing and delay stations.
Definition sn_chain.h:313
Matrix< T > D
(Mq x C) demands at queueing stations
Definition sn_chain.h:314
Matrix< T > mu
The reference's 5th output, mu of sn_get_product_form_params: (Mq x ceil(sum finite N) + max S) with ...
Definition sn_chain.h:325
std::vector< T > lambda
(C) chain arrival rates, zero on a closed chain
Definition sn_chain.h:316
Matrix< T > V
The reference's 7th output: Vchain with the Source and Join station rows removed, (M - Source - Join)...
Definition sn_chain.h:330
Matrix< T > Z
(Md x C) demands at delay stations
Definition sn_chain.h:315
std::vector< std::size_t > delay_stations
1-based
Definition sn_chain.h:320
std::vector< double > N
(C) populations
Definition sn_chain.h:317
std::vector< std::size_t > queue_stations
1-based
Definition sn_chain.h:319
std::vector< double > S
(Mq) server counts
Definition sn_chain.h:318