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(
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 for (std::size_t r = 0; r < K; ++r) {
171 T q = mor.Q[r];
172 if (!(q >= zero)) q = zero; // outside the regime the expansion can leave
173 if (q > Npop[r]) q = Npop[r]; // [0,N]; clamp rather than report an impossible
174 const T x = T((Npop[r] - q) / Z[r]);
175 Q(iDps, r) = q;
176 Q(iInf, r) = T(Npop[r] - q); // population conservation (exact, closed)
177 X[r] = x;
178 for (std::size_t i = 0; i < M; ++i) Tp(i, r) = T(x * V(i, r));
179 U(iInf, r) = Q(iInf, r); // INF utilization convention
180 U(iDps, r) = T(x * V(iDps, r) * S[r] / c);
181 if (x > zero) C[r] = T(Npop[r] / x);
182 }
183 for (std::size_t i = 0; i < M; ++i)
184 for (std::size_t r = 0; r < K; ++r)
185 if (Tp(i, r) > zero) R(i, r) = T(Q(i, r) / Tp(i, r));
186
187 out.sol.Q = Q;
188 out.sol.U = U;
189 out.sol.R = R;
190 out.sol.Tp = Tp;
191 out.sol.X = X;
192 out.sol.C = C;
193 out.sol.iter = 1;
194 out.sol.lG = std::numeric_limits<double>::quiet_NaN(); // not product-form
195 out.sol.method = "morrison";
196 out.actualmethod = "morrison";
197 }
198 return out;
199}
200
201} // namespace nc
202} // namespace line
203
204#endif // LINE_SOLVERS_NC_DPS_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
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:181
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.
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 [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
One job class of the network.
double population
infinite for an open class