LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_pf_params.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_SN_PF_PARAMS_H
6#define LINE_SOLVERS_NC_SN_PF_PARAMS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `matlab/src/api/sn/sn_get_product_form_params.m`: the CLASS-level
12 * product-form parameters.
13 *
14 * This is the sibling of `sn_get_product_form_chain_params` in
15 * `solvers/mva/sn_chain.h`, and the difference is the whole point of having
16 * both. The chain version aggregates classes into chains and is what the
17 * mean-value solvers want; this one keeps the classes apart, which is what the
18 * SOJOURN-TIME distribution needs -- `pfqn_stdf` conditions on a tagged job of
19 * a named class, and a chain-aggregated demand has no such job in it.
20 *
21 * The demand is normalized by the visits of the chain's REFERENCE CLASS at the
22 * reference station when the chain has one, and left unnormalized when it does
23 * not, exactly as the reference does. That divisor is what makes D a demand per
24 * system completion rather than per visit.
25 *
26 * `mu` is wider than the population on purpose: the reference sizes it
27 * `ceil(sum N) + max(S)` because `pfqn_mvaldmx` indexes past |N| by the server
28 * count. Narrowing it to |N| is a silent out-of-range read there.
29 */
30
31#include <algorithm>
32#include <cmath>
33#include <cstddef>
34#include <vector>
35
37#include "line/num/number.h"
38#include "line/util/matrix.h"
39
40namespace line {
41namespace nc {
42
43/** The `[lambda,D,N,Z,mu,S,V]` of the reference. */
44template <class T>
45struct PfParams {
46 std::vector<T> lambda; ///< (R) arrival rate, zero on a closed class
47 Matrix<T> D; ///< (Mq x R) demand at the queueing stations
48 std::vector<double> N; ///< (R) population, infinite on an open class
49 Matrix<T> Z; ///< (max(1,Mz) x R) demand at the delay stations
50 Matrix<T> mu; ///< (Mq x ceil(sum N)+max S) rate lattice
51 std::vector<double> S; ///< (Mq) server counts
52 Matrix<T> V; ///< (M x R) visits summed over chains
53 std::vector<std::size_t> queue_stations; ///< (Mq) 1-based station indices
54 std::vector<std::size_t> delay_stations; ///< (Mz) 1-based station indices
55};
56
57/**
58 * Port of `sn_get_product_form_params`.
59 *
60 * @param sn the refreshed struct
61 */
62template <class T>
64 const T zero = num_traits<T>::from_int(0);
65 const T one = num_traits<T>::from_int(1);
66 const std::size_t R = sn.nclasses;
67
69 p.N.assign(R, 0.0);
70 for (std::size_t r = 0; r < R; ++r) p.N[r] = sn.classes[r].population;
71
72 // The reference selects on NODE type, not on the scheduling discipline: a
73 // Delay is an infinite server and a Queue is not, whatever its sched.
74 std::size_t sourceStation = 0;
75 for (std::size_t i = 0; i < sn.nstations; ++i) {
76 const qn::NodeType nt = sn.stations[i].nodetype;
77 if (nt == qn::NodeType::Queue) p.queue_stations.push_back(i + 1);
78 else if (nt == qn::NodeType::Delay) p.delay_stations.push_back(i + 1);
79 else if (nt == qn::NodeType::Source) sourceStation = i + 1;
80 }
81 const std::size_t Mq = p.queue_stations.size(), Mz = p.delay_stations.size();
82
83 p.lambda.assign(R, zero);
84 for (std::size_t r = 0; r < R; ++r)
85 if (std::isinf(p.N[r]) && sourceStation > 0 && !sn.disabled[sourceStation - 1][r])
86 p.lambda[r] = sn.rates(sourceStation - 1, r);
87
88 p.S.assign(Mq, 1.0);
89 double maxS = 1.0;
90 for (std::size_t i = 0; i < Mq; ++i) {
91 p.S[i] = sn.stations[p.queue_stations[i] - 1].nservers;
92 if (std::isfinite(p.S[i]) && p.S[i] > maxS) maxS = p.S[i];
93 }
94
95 double Nct = 0.0;
96 for (double n : p.N)
97 if (std::isfinite(n)) Nct += n;
98 const std::size_t width =
99 static_cast<std::size_t>(std::ceil(Nct)) + static_cast<std::size_t>(maxS);
100
101 // The per-class demand: visits at this station over the service rate, over
102 // the reference class's visits at the reference station.
103 const auto demand = [&](std::size_t ist, std::size_t r) {
104 std::size_t c = sn.nchains;
105 for (std::size_t cc = 0; cc < sn.nchains; ++cc)
106 if (sn.chains[cc][r]) { c = cc; break; }
107 if (c == sn.nchains || sn.disabled[ist - 1][r] || sn.rates(ist - 1, r) == zero)
108 return zero; // MATLAB divides by NaN here and clears with D(isnan(D))=0
109 const std::size_t sf = sn.stateful_of_station(ist) - 1;
110 T num = T(sn.visits[c](sf, r) / sn.rates(ist - 1, r));
111 if (sn.refclass[c] > 0) {
112 const std::size_t rsf = sn.stateful_of_station(sn.classes[r].refstat) - 1;
113 const T den = sn.visits[c](rsf, sn.refclass[c] - 1);
114 if (den == zero) return zero;
115 num = T(num / den);
116 }
117 return num;
118 };
119
120 p.D = Matrix<T>(Mq, R, zero);
121 p.mu = Matrix<T>(Mq, std::max<std::size_t>(1, width), one);
122 for (std::size_t i = 0; i < Mq; ++i) {
123 for (std::size_t r = 0; r < R; ++r) p.D(i, r) = demand(p.queue_stations[i], r);
124 for (std::size_t n = 1; n <= p.mu.cols(); ++n)
125 p.mu(i, n - 1) = num_traits<T>::from_double(
126 std::min<double>(static_cast<double>(n), p.S[i]));
127 }
128
129 p.Z = Matrix<T>(std::max<std::size_t>(1, Mz), R, zero);
130 for (std::size_t i = 0; i < Mz; ++i)
131 for (std::size_t r = 0; r < R; ++r) p.Z(i, r) = demand(p.delay_stations[i], r);
132
133 p.V = Matrix<T>(sn.nstations, R, zero);
134 for (std::size_t c = 0; c < sn.nchains; ++c)
135 for (std::size_t i = 0; i < sn.nstations; ++i) {
136 const std::size_t sf = sn.stateful_of_station(i + 1) - 1;
137 for (std::size_t r = 0; r < R; ++r) p.V(i, r) = T(p.V(i, r) + sn.visits[c](sf, r));
138 }
139 return p;
140}
141
142} // namespace nc
143} // namespace line
144
145#endif // LINE_SOLVERS_NC_SN_PF_PARAMS_H
A network plus its refreshed NetworkStruct.
Dense matrix and non-owning view.
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:324
PfParams< T > sn_get_product_form_params(const qn::NetworkStruct< T > &sn)
Port of sn_get_product_form_params.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
The [lambda,D,N,Z,mu,S,V] of the reference.
std::vector< double > S
(Mq) server counts
Matrix< T > Z
(max(1,Mz) x R) demand at the delay stations
Matrix< T > V
(M x R) visits summed over chains
std::vector< double > N
(R) population, infinite on an open class
Matrix< T > D
(Mq x R) demand at the queueing stations
std::vector< std::size_t > queue_stations
(Mq) 1-based station indices
std::vector< T > lambda
(R) arrival rate, zero on a closed class
std::vector< std::size_t > delay_stations
(Mz) 1-based station indices
Matrix< T > mu
(Mq x ceil(sum N)+max S) rate lattice