LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_nc_dps.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_DPS_H
6#define LINE_SOLVERS_NC_DPS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Heavy-usage asymptotic analysis of the closed two-station network with one think
12 * (infinite-server) station and one discriminatory processor-sharing station, by the
13 * generating-function expansion of J.A. Morrison, "Asymptotic analysis of a large closed queueing
14 * network with discriminatory processor sharing", Queueing Systems 9 (1991) 191-214.
15 *
16 * Port of matlab/src/solvers/NC/nc_is_dps_model.m and solver_nc_dps_analyzer.m, matching
17 * jar/src/main/java/jline/solvers/nc/analyzers/Solver_nc_dps_analyzer.java and
18 * python/line_solver/solvers/solver_nc/solver_nc_dps_analyzer.py. The kernel is
19 * line/api/npfqn/npfqn_dps_morrison.h.
20 *
21 * THERE IS NO NORMALIZING CONSTANT HERE. A DPS station is not product-form -- that is the premise
22 * of the paper -- so lG is NaN, as on the maximum-entropy route. NC hosts this method because NC is
23 * where LINE keeps the asymptotic expansions of generating functions and normalizing-constant
24 * integrals (pana, mmint2, le, ble, gleint, rayint), which is the family Morrison's expansion
25 * belongs to, not because a constant is being computed.
26 *
27 * Response times come from Little's law on the queue-length result rather than from the expanded
28 * RESULT 2 (eq. 4.17), so that Q = R*T holds exactly in the returned table; the two agree to the
29 * order of the approximation, since Morrison derives (4.17) as the ratio (4.11)/(4.15).
30 */
31
32#include <cmath>
33#include <limits>
34#include <string>
35#include <vector>
36
39#include "line/num/number.h"
41#include "line/solvers/nc/solver_nc_oi.h" // detail::oi_visits, shared with the OI route
42#include "line/util/error.h"
43#include "line/util/matrix.h"
44
45namespace line {
46namespace nc {
47
48/** True when any station of the network is scheduled DPS. */
49template <class T>
51 for (std::size_t i = 0; i < sn.nstations; ++i)
52 if (sn.stations[i].sched == qn::SchedStrategy::DPS) return true;
53 return false;
54}
55
56/**
57 * True when the model is the closed two-station network Morrison's expansion is derived for: one
58 * infinite-server (think) station and one single-server DPS station, exponential service, every
59 * class alternating between the two. The shape is checked exactly, not approximately: outside it
60 * the expansion has no derivation behind it.
61 */
62template <class T>
65 if (sn.nstations != 2) return false;
66 bool anyPositive = false;
67 for (const qn::JobClass& c : sn.classes) {
68 if (std::isinf(c.population)) return false;
69 if (c.population > 0) anyPositive = true;
70 }
71 if (!anyPositive) return false;
72
73 std::size_t iInf = sn.nstations, iDps = sn.nstations;
74 for (std::size_t i = 0; i < sn.nstations; ++i) {
75 const SchedStrategy s = sn.stations[i].sched;
76 if (s == SchedStrategy::INF) {
77 if (iInf != sn.nstations) return false;
78 iInf = i;
79 } else if (s == SchedStrategy::DPS) {
80 if (iDps != sn.nstations) return false;
81 iDps = i;
82 } else {
83 return false;
84 }
85 }
86 if (iInf == sn.nstations || iDps == sn.nstations) return false;
87 const double c = sn.stations[iDps].nservers;
88 if (std::isfinite(c) && c != 1.0) return false; // multi-server DPS is not Morrison's share
89 if (sn.nchains != sn.nclasses) return false; // class switching
90 for (std::size_t ch = 0; ch < sn.nchains; ++ch)
91 if (sn.inchain[ch].size() > 1) return false;
92
93 for (std::size_t r = 0; r < sn.nclasses; ++r) {
94 if (!(sn.classes[r].population > 0)) return false;
95 const std::size_t sts[2] = {iInf, iDps};
96 for (std::size_t k = 0; k < 2; ++k) {
97 const double rate = num_traits<T>::to_double(sn.rates(sts[k], r));
98 if (!std::isfinite(rate) || rate <= 0) return false;
99 const double scv = num_traits<T>::to_double(sn.scv(sts[k], r));
100 if (std::isfinite(scv) && std::fabs(scv - 1.0) > 1e-6) return false;
101 }
102 if (sn.stations[iDps].schedparam.size() != sn.nclasses) return false;
103 const double wgt = num_traits<T>::to_double(sn.stations[iDps].schedparam[r]);
104 if (!std::isfinite(wgt) || wgt <= 0) return false;
105 }
106
107 const Matrix<T> V = detail::oi_visits(sn);
108 for (std::size_t r = 0; r < sn.nclasses; ++r) {
109 const double vi = num_traits<T>::to_double(V(iInf, r));
110 const double vd = num_traits<T>::to_double(V(iDps, r));
111 if (std::fabs(vi - vd) > 1e-9 * std::max(1.0, vi)) return false; // unequal visits
112 }
113 return true;
114}
115
116/**
117 * Analyzes the closed think+DPS network.
118 *
119 * @param sn the network structure, of the shape nc_is_dps_model accepts
120 * @param opt solver options
121 * @return the mean performance measures, with lG = NaN
122 */
123template <class T>
125 (void)opt;
126 NcSolution<T> out;
127 if constexpr (!num_traits<T>::has_transcendental) {
128 (void)sn;
129 throw UnsupportedError(ErrorKind{"Arithmetic"},
130 "solver_nc_dps_analyzer: Morrison's W_m are erfc integrals; this backend has no "
131 "transcendental arithmetic");
132 } else {
133 using qn::SchedStrategy;
134 // The shape is re-checked here, not assumed from the caller: this analyzer is
135 // reachable from the runner, the dispatch chain and directly from user code, and
136 // every quantity below -- think time, DPS service time, weights, visit ratios --
137 // is meaningless off the shape the expansion was derived for.
138 if (!nc_is_dps_model(sn))
139 throw InputError(
140 "solver_nc_dps_analyzer: applies only to a CLOSED network of exactly two stations, "
141 "one infinite-server (think) station and one single-server DPS station with "
142 "exponential service and one visit each per cycle (see nc_is_dps_model).");
143 const T zero = num_traits<T>::from_int(0);
144 const std::size_t M = sn.nstations, K = sn.nclasses;
145 std::size_t iInf = M, iDps = M;
146 for (std::size_t i = 0; i < M; ++i) {
147 if (sn.stations[i].sched == SchedStrategy::INF) iInf = i;
148 if (sn.stations[i].sched == SchedStrategy::DPS) iDps = i;
149 }
150 if (iInf == M || iDps == M)
151 throw InputError("solver_nc_dps_analyzer: requires one INF and one DPS station");
152
153 std::vector<T> Npop(K, zero), Z(K, zero), S(K, zero), w(K, zero);
154 for (std::size_t r = 0; r < K; ++r) {
155 Npop[r] = num_traits<T>::from_double(sn.classes[r].population);
156 Z[r] = T(num_traits<T>::from_int(1) / sn.rates(iInf, r));
157 S[r] = T(num_traits<T>::from_int(1) / sn.rates(iDps, r));
158 w[r] = sn.stations[iDps].schedparam[r];
159 }
160
162
163 const Matrix<T> V = detail::oi_visits(sn);
164 Matrix<T> Q(M, K, zero), U(M, K, zero), R(M, K, zero), Tp(M, K, zero);
165 std::vector<T> X(K, zero), C(K, zero);
166
167 const double cd = sn.stations[iDps].nservers;
168 const T c = num_traits<T>::from_double((std::isfinite(cd) && cd > 0.0) ? cd : 1.0);
169
170 // Off the moderately-heavy regime the expansion can leave [0,N] or imply a DPS utilization
171 // above one; clamping Q used to hide that and still return the impossible throughputs.
172 {
173 bool bad = false;
174 double udps = 0.0;
175 std::string qs = "[";
176 for (std::size_t r = 0; r < K; ++r) {
177 const double q = num_traits<T>::to_double(mor.Q[r]);
178 const double np = num_traits<T>::to_double(Npop[r]);
179 if (!std::isfinite(q) || q < 0.0 || q > np) bad = true;
180 udps += (np - q) / num_traits<T>::to_double(Z[r]) * num_traits<T>::to_double(S[r]);
181 qs += (r > 0 ? ", " : "") + std::to_string(q);
182 }
183 udps /= num_traits<T>::to_double(c);
184 if (bad || !std::isfinite(udps) || udps > 1.0 + 1e-9) {
185 throw NumericError(ErrorKind{"Infeasible"},
186 "SolverNC method 'morrison': the heavy-usage expansion returned an infeasible result (DPS queue "
187 "lengths " + qs + "], DPS utilization " + std::to_string(udps) + "; usage rho="
188 + std::to_string(num_traits<T>::to_double(mor.rho)) + ", a=" + std::to_string(num_traits<T>::to_double(mor.a))
189 + "). The model is outside the moderately-heavy regime the expansion assumes; use SolverCTMC, "
190 "SolverJMT or SolverLDES instead.");
191 }
192 }
193 for (std::size_t r = 0; r < K; ++r) {
194 const T q = mor.Q[r];
195 const T x = T((Npop[r] - q) / Z[r]);
196 Q(iDps, r) = q;
197 Q(iInf, r) = T(Npop[r] - q); // population conservation (exact, closed)
198 X[r] = x;
199 for (std::size_t i = 0; i < M; ++i) Tp(i, r) = T(x * V(i, r));
200 U(iInf, r) = Q(iInf, r); // INF utilization convention
201 U(iDps, r) = T(x * V(iDps, r) * S[r] / c);
202 if (x > zero) C[r] = T(Npop[r] / x);
203 }
204 for (std::size_t i = 0; i < M; ++i)
205 for (std::size_t r = 0; r < K; ++r)
206 if (Tp(i, r) > zero) R(i, r) = T(Q(i, r) / Tp(i, r));
207
208 out.sol.Q = Q;
209 out.sol.U = U;
210 out.sol.R = R;
211 out.sol.Tp = Tp;
212 out.sol.X = X;
213 out.sol.C = C;
214 out.sol.iter = 1;
215 out.sol.lG = std::numeric_limits<double>::quiet_NaN(); // not product-form
216 out.sol.method = "morrison";
217 out.actualmethod = "morrison";
218 }
219 return out;
220}
221
222} // namespace nc
223} // namespace line
224
225#endif // LINE_SOLVERS_NC_DPS_H
InputError(const std::string &what)
Definition error.h:105
NumericError(const std::string &what)
Definition error.h:112
UnsupportedError(const std::string &what)
Definition error.h:119
A network plus its refreshed NetworkStruct.
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
NcSolution< T > solver_nc_dps_analyzer(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Analyzes the closed think+DPS network.
bool sn_has_dps(const qn::NetworkStruct< T > &sn)
True when any station of the network is scheduled DPS.
bool nc_is_dps_model(const qn::NetworkStruct< T > &sn)
True when the model is the closed two-station network Morrison's expansion is derived for: one infini...
DpsMorrisonResult< T > npfqn_dps_morrison(const std::vector< T > &N, const std::vector< T > &Z, const std::vector< T > &S, const std::vector< T > &w)
Evaluates Morrison's two-term approximation.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Controls and result shape shared by the normalizing-constant analyzers.
A queueing network and its refreshed NetworkStruct.
Two-term heavy-usage asymptotic approximation for a closed queueing network with one infinite-server ...
Number-type abstraction for the templated API port.
Order-independent (OI) and pass-and-swap (P&S) normalizing-constant analysis.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The [Q,U,R,T,C,X,lG] of the reference, plus the algorithm that ran.
Definition nc_types.h:113
mva::MvaSolution< T > sol
Definition nc_types.h:114
std::string actualmethod
Definition nc_types.h:115
Controls, defaulting to SolverOptions('NC') in the reference.
Definition nc_types.h:33
Mean queue lengths, sojourn times and throughputs, with Morrison's intermediate constants.
std::vector< T > Q
mean number of class-k jobs at the DPS station
T a
heavy-usage parameter a = 1 - rho at the N = 1 scale
One job class of the network.
double population
infinite for an open class