LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_nc.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_SOLVER_NC_H
6#define LINE_SOLVERS_NC_SOLVER_NC_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_nc.m`: the load-INDEPENDENT normalizing-constant analyzer.
12 *
13 * WHAT IT COMPUTES. One evaluation of the normalizing constant G(N) settles the
14 * whole product-form network: the chain throughput is X_c = G(N - 1_c) / G(N),
15 * and the chain queue length at station i follows from a constant evaluated on
16 * the model with station i REPLICATED into a private class,
17 *
18 * Q_ic = Zms_ic X_c + Lms_ic exp(lG_ar(i,c) - lG),
19 *
20 * which is the arrival-theorem identity written in constants rather than in a
21 * recursion. That is why this file calls `pfqn_nc` once per chain and once per
22 * (station, chain) pair rather than solving anything itself: the algorithms
23 * live in `api/pfqn`, and choosing among them is `pfqn_nc`'s job.
24 *
25 * MULTISERVER IS SEIDMANN'S APPROXIMATION, NOT AN EXACT SOLVE. A station with
26 * c > 1 servers is split into a demand L/c at a single server plus a pure delay
27 * L (c-1)/c. It is exact only in the limit; the reference warns and continues,
28 * and `@@SolverNC/runAnalyzer` converts genuine multiservers to load-dependent
29 * rates BEFORE reaching here whenever the model is product-form, so this branch
30 * serves the models that have no exact load-dependent form.
31 *
32 * WHY THERE IS AN OUTER LOOP AT ALL. `SolverOptions('NC')` sets
33 * `config.highvar = 'interp'`, so a non-product-form FCFS station has its
34 * service time rescaled by `npfqn_nonexp_approx` after each pass and the
35 * analyzer re-solves until the decay rates eta stop moving. With no FCFS
36 * station the reference pins iter_max to 1 and the loop runs once.
37 *
38 * ARITHMETIC. Every measure here is formed from differences of LOGARITHMS of
39 * normalizing constants, which is the only way the ratios stay representable;
40 * there is no exact-field formulation of exp(lG' - lG), so a non-transcendental
41 * backend is refused by name rather than narrowed.
42 */
43
44#include <algorithm>
45#include <cmath>
46#include <limits>
47#include <string>
48#include <vector>
49
58#include "line/util/error.h"
59#include "line/util/matrix.h"
60
61namespace line {
62namespace nc {
63
64using lang::GlobalConstants;
66
67namespace detail {
68
69/** `sn.njobs` per chain, infinite when any class of the chain is open. */
70template <class T>
71std::vector<double> chain_population(const qn::NetworkStruct<T>& sn) {
72 std::vector<double> Nchain(sn.nchains, 0.0);
73 for (std::size_t c = 0; c < sn.nchains; ++c) {
74 double n = 0.0;
75 bool open = false;
76 for (std::size_t k : sn.inchain[c]) {
77 const double p = sn.classes[k - 1].population;
78 if (std::isinf(p)) open = true;
79 n += p;
80 }
81 Nchain[c] = open ? std::numeric_limits<double>::infinity() : n;
82 }
83 return Nchain;
84}
85
86/**
87 * The population vector `pfqn_nc` wants: integral counts, with an OPEN chain
88 * marked by a negative entry.
89 *
90 * MATLAB marks an open class by N_r = Inf and zeroes it inside pfqn_nc; the
91 * exact-capable port has no infinity and uses the sign instead, which is also
92 * the .qn interchange convention.
93 */
94inline std::vector<int> nc_population(const std::vector<double>& Nchain) {
95 std::vector<int> N(Nchain.size(), 0);
96 for (std::size_t c = 0; c < Nchain.size(); ++c)
97 N[c] = std::isinf(Nchain[c]) ? -1 : static_cast<int>(std::llround(Nchain[c]));
98 return N;
99}
100
101/**
102 * `cellsum(sn.visits)` at STATION level: the visit ratios summed over chains.
103 *
104 * The reference passes this to npfqn_nonexp_approx, which never reads it; it is
105 * built anyway so the argument list matches the reference one for one.
106 */
107template <class T>
108Matrix<T> station_visits(const qn::NetworkStruct<T>& sn) {
109 const T zero = num_traits<T>::from_int(0);
110 Matrix<T> V(sn.nstations, sn.nclasses, zero);
111 for (std::size_t c = 0; c < sn.nchains; ++c)
112 for (std::size_t i = 0; i < sn.nstations; ++i) {
113 const std::size_t sf = sn.stateful_of_station(i + 1);
114 for (std::size_t k = 0; k < sn.nclasses; ++k)
115 V(i, k) = T(V(i, k) + sn.visits[c](sf - 1, k));
116 }
117 return V;
118}
119
120/** `oner(N, r)`: one job of chain r removed. */
121inline std::vector<int> oner(std::vector<int> N, std::size_t r) {
122 if (N[r] > 0) --N[r];
123 return N;
124}
125
126/** Map `options.method` onto the pfqn dispatcher, refusing an unknown name. */
127inline pfqn::NcMethod nc_pfqn_method(const std::string& method) {
128 return pfqn::nc_method_of(method);
129}
130
131} // namespace detail
132
133/**
134 * Port of `solver_nc.m`.
135 *
136 * @param sn the refreshed struct
137 * @param opt solver controls
138 * @return the class-level measures, the log normalizing constant and the
139 * concrete algorithm the constants were computed with
140 */
141template <class T>
143 NcSolution<T> out;
144 if constexpr (!num_traits<T>::has_transcendental) {
145 (void)sn;
146 (void)opt;
147 throw UnsupportedError(
148 "solver_nc: the normalizing-constant analyzer forms X = exp(lG(N-1_c) - lG(N)) and "
149 "needs transcendental arithmetic; this backend has none");
150 } else {
151 // Krzesinski state-dependent routing: the model has its own product
152 // form (eq. 16), so it is intercepted before the convolution and MVA
153 // analyzers, which assume state-independent routing; see
154 // _kb/16-state-dependent-routing.md
155 if (!sn.sdr.empty()) return solver_nc_sdr(sn, opt);
156
157 const T zero = num_traits<T>::from_int(0);
158 const T one = num_traits<T>::from_int(1);
159 const std::size_t M = sn.nstations, K = sn.nclasses, C = sn.nchains;
160
161 std::vector<double> nservers(M, 1.0);
162 std::vector<bool> isFCFS(M, false);
163 bool anyFCFS = false;
164 for (std::size_t i = 0; i < M; ++i) {
165 nservers[i] = sn.stations[i].nservers;
166 isFCFS[i] = sn.stations[i].sched == SchedStrategy::FCFS;
167 if (isFCFS[i]) anyFCFS = true;
168 }
169
170 // LCFS paired with LCFS-PR is a two-station special case with its own
171 // closed form; nothing else in this file can represent it, since a
172 // non-preemptive LCFS station is not a BCMP station at all.
173 std::vector<std::size_t> lcfsStat, lcfsprStat;
174 for (std::size_t i = 0; i < M; ++i) {
175 if (sn.stations[i].sched == SchedStrategy::LCFS) lcfsStat.push_back(i + 1);
176 if (sn.stations[i].sched == SchedStrategy::LCFSPR) lcfsprStat.push_back(i + 1);
177 }
178 if (!lcfsStat.empty() && !lcfsprStat.empty()) {
179 if (lcfsStat.size() != 1 || lcfsprStat.size() != 1)
180 throw UnsupportedError(
181 "solver_nc: LCFS NC requires exactly one LCFS and one LCFS-PR station");
182 for (std::size_t c = 0; c < C; ++c) {
183 double nc_pop = 0.0;
184 for (std::size_t r : sn.inchain[c]) nc_pop += sn.classes[r - 1].population;
185 if (std::isinf(nc_pop))
186 throw UnsupportedError(
187 "solver_nc: LCFS NC requires a closed queueing network");
188 }
189 // The closed form places every job on one boundary-crossing sequence,
190 // which a self-loop would let a job re-enter without crossing.
191 for (std::size_t i : {lcfsStat[0], lcfsprStat[0]}) {
192 const std::size_t sf = sn.stateful_of_station(i) - 1;
193 for (std::size_t r = 0; r < K; ++r)
194 if (sn.rt(sf * K + r, sf * K + r) > zero)
195 throw UnsupportedError(
196 "solver_nc: LCFS NC does not support self-loops at stations");
197 }
198 out = solver_nc_lcfsqn(sn, opt, lcfsStat[0], lcfsprStat[0]);
199 // STeff is the reference's `ST = 1./sn.rates` with NaN zeroed: an
200 // INFINITE service time (a zero rate) is kept, only a NaN is not.
201 out.STeff = Matrix<T>(M, K, zero);
202 for (std::size_t i = 0; i < M; ++i)
203 for (std::size_t r = 0; r < K; ++r) {
204 const double mu = num_traits<T>::to_double(sn.rates(i, r));
205 if (std::isnan(mu)) continue;
206 out.STeff(i, r) = mu == 0.0
208 std::numeric_limits<double>::infinity())
209 : T(one / sn.rates(i, r));
210 }
211 return out;
212 }
213 if (!lcfsStat.empty())
214 throw UnsupportedError(
215 "solver_nc: LCFS scheduling requires a paired LCFS-PR station");
216
217 const std::vector<double> Nchain = detail::chain_population(sn);
218 const std::vector<int> Nnc = detail::nc_population(Nchain);
219 std::vector<std::size_t> openChains, closedChains;
220 for (std::size_t c = 0; c < C; ++c)
221 (std::isinf(Nchain[c]) ? openChains : closedChains).push_back(c);
222
223 const pfqn::NcMethod pmethod = detail::nc_pfqn_method(opt.method);
224 pfqn::NcOptions popt;
225 popt.samples = opt.samples;
226 popt.seed = opt.seed;
227 popt.tol = opt.tol;
228 popt.aghq_nodes = opt.aghq_nodes;
229 popt.mcmc_batches = opt.mcmc_batches;
230 popt.mcmc_burnin = opt.mcmc_burnin;
231 const T atol = num_traits<T>::from_double(opt.tol);
232
233 // Chain aggregation, and the class-level service times the outer loop
234 // rescales. ST0 is the untouched original: npfqn_nonexp_approx is always
235 // handed the original times, never its own previous output.
237 Matrix<T> ST = d.ST, ST0 = d.ST;
238 const Matrix<T> V = detail::station_visits(sn);
239 // sn.scv with the disabled pairs marked NOT FINITE, which is what
240 // isfinite(SCV) selects on in the reference: sn.scv is NaN there, and
241 // this port carries the marker in `disabled` instead.
242 Matrix<T> SCVnan = sn.scv;
243 for (std::size_t i = 0; i < M; ++i)
244 for (std::size_t k = 0; k < K; ++k)
245 if (sn.disabled[i][k])
246 SCVnan(i, k) = num_traits<T>::from_double(
247 std::numeric_limits<double>::quiet_NaN());
248
249 std::vector<T> gamma(M, zero);
250 std::vector<T> nserv_t(M, one);
251 for (std::size_t i = 0; i < M; ++i) nserv_t[i] = num_traits<T>::from_double(nservers[i]);
252
253 std::vector<T> lambda(C, zero);
254 std::vector<T> eta(M, one), eta_1(M, zero);
255 const int iter_max = anyFCFS ? opt.iter_max : 1;
256 int it = 0;
257 double lG = 0.0;
258 std::string actualmethod = opt.method;
260
261 while (it < iter_max) {
262 { // the reference's max(abs(1 - eta./eta_1)) > iter_tol test
263 double dev = 0.0;
264 for (std::size_t i = 0; i < M; ++i) {
265 const double e1 = num_traits<T>::to_double(eta_1[i]);
266 const double e = num_traits<T>::to_double(eta[i]);
267 const double v = std::fabs(1.0 - e / e1);
268 if (!(v <= dev)) dev = v; // NaN and Inf both count as "not converged"
269 }
270 if (!(dev > opt.iter_tol)) break;
271 }
272 ++it;
273 eta_1 = eta;
274
275 if (it == 1) {
276 // An open chain's reference station is its Source, whose chain
277 // service time is 1 / total arrival rate.
278 for (std::size_t c = 0; c < C; ++c) {
279 if (!std::isinf(Nchain[c])) continue;
280 const std::size_t rst = sn.classes[sn.inchain[c][0] - 1].refstat;
281 if (d.STchain(rst - 1, c) != zero)
282 lambda[c] = T(one / d.STchain(rst - 1, c));
283 }
284 } else {
285 for (std::size_t c = 0; c < C; ++c)
286 for (std::size_t i = 0; i < M; ++i) {
287 T st = zero;
288 for (std::size_t k : sn.inchain[c]) st += ST(i, k - 1) * d.alpha(i, k - 1);
289 d.STchain(i, c) = st;
290 d.Lchain(i, c) = T(d.Vchain(i, c) * st);
291 }
292 }
293 for (std::size_t i = 0; i < M; ++i)
294 for (std::size_t c = 0; c < C; ++c) {
295 if (!std::isfinite(num_traits<T>::to_double(d.STchain(i, c))))
296 d.STchain(i, c) = zero;
297 if (!std::isfinite(num_traits<T>::to_double(d.Lchain(i, c))))
298 d.Lchain(i, c) = zero;
299 }
300
301 // Seidmann's approximation: a c-server station becomes a demand L/c
302 // at one server plus a pure delay L (c-1)/c.
303 Matrix<T> Lms(M, C, zero), Z(M, C, zero), Zms(M, C, zero);
304 std::vector<std::size_t> infServers;
305 for (std::size_t i = 0; i < M; ++i) {
306 if (std::isinf(nservers[i])) {
307 infServers.push_back(i);
308 for (std::size_t c = 0; c < C; ++c) Z(i, c) = d.Lchain(i, c);
309 } else {
310 const T cs = num_traits<T>::from_double(nservers[i]);
311 for (std::size_t c = 0; c < C; ++c) {
312 Lms(i, c) = T(d.Lchain(i, c) / cs);
313 Zms(i, c) = T(d.Lchain(i, c) *
314 num_traits<T>::from_double(nservers[i] - 1.0) / cs);
315 }
316 }
317 }
318 Matrix<T> Ztot(1, C, zero);
319 for (std::size_t c = 0; c < C; ++c)
320 for (std::size_t i = 0; i < M; ++i) Ztot(0, c) += Z(i, c) + Zms(i, c);
321
322 // step 1: the base constant
323 const pfqn::NcDispatchResult<T> base =
324 pfqn::pfqn_nc(lambda, Lms, Nnc, Ztot, pmethod, atol, popt);
325 if (!base.valid) {
326 // The method declined this model (the reference's empty lG).
327 // MATLAB returns every output empty here and `getAvg` renders a
328 // table of ZEROS while reporting a completed analysis; that
329 // convention is reproduced deliberately, ruled by the user on
330 // 2026-07-25 (register row N1). The consequence the ruling
331 // accepts: a caller cannot tell "no jobs" from "declined".
332 out.sol.Q = Matrix<T>(M, K, zero);
333 out.sol.U = Matrix<T>(M, K, zero);
334 out.sol.R = Matrix<T>(M, K, zero);
335 out.sol.Tp = Matrix<T>(M, K, zero);
336 out.sol.X.assign(K, zero);
337 out.sol.C.assign(K, zero);
338 out.sol.lG = 0.0;
339 out.sol.iter = 1;
340 out.sol.method = opt.method;
341 out.actualmethod = opt.method;
342 out.STeff = ST;
343 return out;
344 }
345 lG = base.lG;
346 actualmethod = base.method;
347 std::vector<T> Xchain = base.X;
348 Matrix<T> Qchain = base.Q;
349
350 // Seidmann's delay term makes the by-product measures inconsistent
351 // with the approximated model, so the reference discards them and
352 // takes the constant-ratio route below. 'mcmc' is EXEMPT: it obtains
353 // X and Q from ONE simulation of the regularized network, so
354 // discarding them would cost R + M*R further simulations to recover
355 // the same means by differencing lG. The surrogate delay is added
356 // back to Qchain in the fill branch below instead.
357 bool allZms = true;
358 for (std::size_t c = 0; c < C; ++c) {
359 T s = zero;
360 for (std::size_t i = 0; i < M; ++i) s += Zms(i, c);
361 if (!(num_traits<T>::to_double(s) > GlobalConstants::FineTol)) allZms = false;
362 }
363 if (allZms && pmethod != pfqn::NcMethod::Mcmc) {
364 Xchain.clear();
365 Qchain = Matrix<T>();
366 }
367
368 if (Xchain.empty()) {
369 Xchain = lambda;
370 Qchain = Matrix<T>(M, C, zero);
371 for (std::size_t r : closedChains) {
372 const std::vector<int> Nr = detail::oner(Nnc, r);
373 const pfqn::NcDispatchResult<T> sub =
374 pfqn::pfqn_nc(lambda, Lms, Nr, Ztot, pmethod, atol, popt);
375 if (!sub.valid) {
376 // The reference returns empty from the SUBPROBLEMS too,
377 // rather than assigning [] into a scalar slot.
378 out.sol.Q = Matrix<T>(M, K, zero);
379 out.sol.U = Matrix<T>(M, K, zero);
380 out.sol.R = Matrix<T>(M, K, zero);
381 out.sol.Tp = Matrix<T>(M, K, zero);
382 out.sol.X.assign(K, zero);
383 out.sol.C.assign(K, zero);
384 out.sol.lG = 0.0;
385 out.sol.iter = 1;
386 out.sol.method = opt.method;
387 out.actualmethod = opt.method;
388 out.STeff = ST;
389 return out;
390 }
391 const double lGr = sub.lG;
392 Xchain[r] = num_traits<T>::from_double(std::exp(lGr - lG));
393 for (std::size_t i = 0; i < M; ++i) {
394 if (!(d.Lchain(i, r) > zero)) continue;
395 if (std::isinf(nservers[i])) {
396 Qchain(i, r) = T(d.Lchain(i, r) * Xchain[r]);
397 continue;
398 }
399 // Station i replicated into a private class holding one
400 // job: the arrival theorem in normalizing-constant form.
401 Matrix<T> Lar(M, C + 1, zero);
402 std::size_t row = 0;
403 for (std::size_t i2 = 0; i2 < M; ++i2) {
404 if (i2 == i) continue;
405 for (std::size_t c = 0; c < C; ++c) Lar(row, c) = Lms(i2, c);
406 ++row;
407 }
408 for (std::size_t c = 0; c < C; ++c) Lar(row, c) = Lms(i, c);
409 Lar(row, C) = one;
410 std::vector<T> lam_ar = lambda;
411 lam_ar.push_back(zero);
412 std::vector<int> N_ar = Nr;
413 N_ar.push_back(1);
414 Matrix<T> Z_ar(1, C + 1, zero);
415 for (std::size_t c = 0; c < C; ++c) Z_ar(0, c) = Ztot(0, c);
417 pfqn::pfqn_nc(lam_ar, Lar, N_ar, Z_ar, pmethod, atol, popt);
418 // The most costly call of the whole solve, so it is the
419 // one whose algorithm is reported.
420 actualmethod = ar.method;
421 Qchain(i, r) = T(Zms(i, r) * Xchain[r] +
422 Lms(i, r) * num_traits<T>::from_double(
423 std::exp(ar.lG - lG)));
424 }
425 }
426 for (std::size_t i = 0; i < M; ++i)
427 for (std::size_t c = 0; c < C; ++c)
428 if (std::isnan(num_traits<T>::to_double(Qchain(i, c)))) Qchain(i, c) = zero;
429
430 // An open chain sees the closed population as extra load: the
431 // mixed-network queue length of Bruell and Balbo.
432 for (std::size_t r : openChains)
433 for (std::size_t i = 0; i < M; ++i) {
434 T load = zero;
435 for (std::size_t o : openChains) load += lambda[o] * d.Lchain(i, o);
436 const T den =
437 std::isinf(nservers[i])
438 ? one
439 : T(one - load / num_traits<T>::from_double(nservers[i]));
440 if (den == zero) continue;
441 T qc = zero;
442 for (std::size_t cc : closedChains) qc += Qchain(i, cc);
443 Qchain(i, r) = T(lambda[r] * d.Lchain(i, r) / den * (one + qc));
444 }
445 } else {
446 // The method returned the measures, so they are KEPT: only the delay
447 // stations, which it does not model, are filled in, and the population
448 // Seidmann's surrogate delay holds outside a multiserver queueing station
449 // is restored. Zms is zero at a single-server station, so the second arm
450 // is a no-op on a single-server model. Rebuilding Qchain from zero here
451 // instead would throw away the queueing-station queue lengths the method
452 // just produced, which is what the reference keeps.
453 if (Qchain.rows() != M || Qchain.cols() != C) Qchain = Matrix<T>(M, C, zero);
454 for (std::size_t c = 0; c < C; ++c)
455 for (std::size_t i = 0; i < M; ++i) {
456 if (!(d.Lchain(i, c) > zero)) continue;
457 if (std::isinf(nservers[i]))
458 Qchain(i, c) = T(d.Lchain(i, c) * Xchain[c]);
459 else if (nservers[i] > 1.0)
460 Qchain(i, c) = T(Qchain(i, c) + Zms(i, c) * Xchain[c]);
461 }
462 }
463
464 Matrix<T> Rchain(M, C, zero), Tchain(M, C, zero);
465 for (std::size_t i = 0; i < M; ++i)
466 for (std::size_t c = 0; c < C; ++c) {
467 if (Xchain[c] != zero && d.Vchain(i, c) != zero)
468 Rchain(i, c) = T(Qchain(i, c) / Xchain[c] / d.Vchain(i, c));
469 Tchain(i, c) = T(Xchain[c] * d.Vchain(i, c));
470 }
471 for (std::size_t i : infServers)
472 for (std::size_t c = 0; c < C; ++c)
473 Rchain(i, c) = d.Vchain(i, c) == zero
474 ? zero
475 : T(d.Lchain(i, c) / d.Vchain(i, c));
476
477 d.ST = ST;
479 Tchain, Xchain);
480 out.STeff = ST; // the effective service times of THIS pass
481
483 opt.highvar, isFCFS, sn.rates, ST0, V, SCVnan, cls.Tp, cls.U, gamma, nserv_t);
484 ST = na.ST;
485 gamma = na.gamma;
486 eta = na.eta;
487 }
488
489 if (it == 0)
490 throw UnsupportedError(
491 "solver_nc: the analyzer made no pass; iter_max must be at least one");
492
493 Matrix<T> Q = cls.Q, U = cls.U, R = cls.R, Tp = cls.Tp;
494 std::vector<T> X = cls.X;
495 auto sanitize = [&](Matrix<T>& A) {
496 for (std::size_t i = 0; i < A.rows(); ++i)
497 for (std::size_t j = 0; j < A.cols(); ++j) {
498 if (A(i, j) < zero) A(i, j) = T(-A(i, j));
499 if (!std::isfinite(num_traits<T>::to_double(A(i, j)))) A(i, j) = zero;
500 }
501 };
502 sanitize(Q);
503 sanitize(U);
504 sanitize(R);
505 for (T& x : X) {
506 if (x < zero) x = T(-x);
507 if (!std::isfinite(num_traits<T>::to_double(x))) x = zero;
508 }
509
510 // Renormalize: the approximations above do not conserve the closed
511 // population exactly, and a chain whose queue lengths do not add up to
512 // N would report a throughput off by the same factor.
513 for (std::size_t c = 0; c < C; ++c) {
514 if (std::isinf(Nchain[c])) continue;
515 T qden = zero;
516 for (std::size_t k : sn.inchain[c])
517 for (std::size_t i = 0; i < M; ++i) qden += Q(i, k - 1);
518 const T ratio = qden > zero
519 ? T(num_traits<T>::from_double(Nchain[c]) / qden)
520 : zero;
521 for (std::size_t k : sn.inchain[c]) {
522 X[k - 1] = T(ratio * X[k - 1]);
523 for (std::size_t i = 0; i < M; ++i) {
524 Q(i, k - 1) = T(ratio * Q(i, k - 1));
525 Tp(i, k - 1) = T(ratio * Tp(i, k - 1));
526 U(i, k - 1) = T(ratio * U(i, k - 1));
527 R(i, k - 1) = Tp(i, k - 1) == zero ? zero : T(Q(i, k - 1) / Tp(i, k - 1));
528 }
529 }
530 }
531
532 out.sol.Q = Q;
533 out.sol.U = U;
534 out.sol.R = R;
535 out.sol.Tp = Tp;
536 out.sol.X = X;
537 out.sol.C.assign(K, zero);
538 for (std::size_t k = 0; k < K; ++k) {
539 const double njobs = sn.classes[k].population;
540 if (std::isfinite(njobs) && X[k] != zero)
541 out.sol.C[k] = T(num_traits<T>::from_double(njobs) / X[k]);
542 else
543 out.sol.C[k] = cls.C[k];
544 }
545 out.sol.lG = lG;
546 out.sol.iter = it;
547 out.sol.method = actualmethod;
548 out.actualmethod = actualmethod;
549 return out;
550 }
551}
552
553} // namespace nc
554} // namespace line
555
556#endif // LINE_SOLVERS_NC_SOLVER_NC_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
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.
The option and result types every MVA analyzer shares.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
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:214
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
Definition sn_chain.h:63
NcSolution< T > solver_nc(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Port of solver_nc.m.
Definition solver_nc.h:142
NcSolution< T > solver_nc_lcfsqn(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t lcfsStat, std::size_t lcfsprStat)
Port of solver_nc_lcfsqn.m.
NcSolution< T > solver_nc_sdr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Solves a network whose entry centre routes by state-dependent routing.
NonexpApproxResult< T > npfqn_nonexp_approx(const std::string &method, const std::vector< bool > &isFCFS, const Matrix< T > &rates, const Matrix< T > &ST, const Matrix< T > &V, const Matrix< T > &SCV, const Matrix< T > &Tput, const Matrix< T > &U, const std::vector< T > &gamma, const std::vector< T > &nservers)
Handler for non-exponential service and arrival processes in AMVA and NC.
NcMethod nc_method_of(const std::string &s)
Map a method name to its enum; throws UnsupportedError on an unknown one.
Definition pfqn_nc.h:177
NcMethod
The methods this port dispatches, one per compute_norm_const case.
Definition pfqn_nc.h:101
@ Mcmc
Chen-O'Cinneide regularization; supplies X and Q, never a constant.
Definition pfqn_nc.h:126
NcDispatchResult< T > pfqn_nc(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, NcMethod method, const T &atol, const NcOptions &nopt)
Normalizing constant of a product-form queueing network: the dispatcher.
Definition pfqn_nc.h:276
Controls and result shape shared by the normalizing-constant analyzers.
A queueing network and its refreshed NetworkStruct.
Handler for non-exponential service and arrival processes in AMVA and NC.
Normalizing constant of a product-form queueing network: the dispatcher.
Chain aggregation and de-aggregation.
The two-station LCFS + LCFS-PR closed network.
Exact product-form analysis under state-dependent routing.
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
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
Class-level results, as sn_deaggregate_chain_results returns them.
Definition sn_chain.h:191
std::vector< T > C
Definition sn_chain.h:193
std::vector< T > X
Definition sn_chain.h:193
static constexpr double FineTol
Definition lang_types.h:668
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
Matrix< T > STeff
the service times of the last pass, MATLAB's STeff
Definition nc_types.h:116
std::string actualmethod
Definition nc_types.h:115
Controls, defaulting to SolverOptions('NC') in the reference.
Definition nc_types.h:33
Return value, mirroring MATLAB's [ST,gamma,nservers,rho,scva,scvs,eta].
Matrix< T > ST
(M x R) scaled service times
std::vector< T > gamma
(M) multiserver asymptotic decay rate
std::vector< T > eta
(M) diffusion decay rate
std::vector< T > X
(R) per-class throughput, empty when not produced
Definition pfqn_nc.h:244
std::string method
the algorithm actually used
Definition pfqn_nc.h:246
Matrix< T > Q
(M x R) queue lengths, empty when not produced
Definition pfqn_nc.h:245
bool valid
False when the METHOD DECLINED THE MODEL, which is the reference's lG = []: pana outside normal usage...
Definition pfqn_nc.h:260
double lG
its logarithm
Definition pfqn_nc.h:243
The options fields compute_norm_const reads beyond the method itself.
Definition pfqn_nc.h:215
double mcmc_burnin
options.config.mcmc_burnin: warm-up fraction pfqn_mcmc discards
Definition pfqn_nc.h:226
unsigned long seed
SolverOptions('NC').seed.
Definition pfqn_nc.h:217
std::size_t mcmc_batches
options.config.mcmc_batches: batches pfqn_mcmc splits its run into
Definition pfqn_nc.h:224
std::size_t aghq_nodes
options.config.aghq_nodes: nodes per simplex direction of the adaptive Gauss-Hermite rule.
Definition pfqn_nc.h:222
std::size_t samples
SolverOptions('NC').samples.
Definition pfqn_nc.h:216
double tol
handed to pfqn_comomrm
Definition pfqn_nc.h:218