LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_nc_lossn.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_LOSSN_H
6#define LINE_SOLVERS_NC_SOLVER_NC_LOSSN_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_nc_lossn_analyzer.m`: the open LOSS NETWORK, which is a
12 * Source, ONE multiclass Delay sitting inside a Finite Capacity Region under a
13 * DROP rule, and a Sink.
14 *
15 * WHAT THE ANALYZER ACTUALLY COMPUTES. There is no queueing: the single station
16 * is an infinite server, so a job that is admitted never waits and leaves after
17 * one service time. The only question is which arrivals are ADMITTED, and the
18 * region answers it through a linear admission rule A n <= C on the per-class
19 * occupancy vector n. Everything else -- carried throughput, mean population,
20 * response time -- follows from the per-class blocking probability by Little's
21 * law at the infinite server.
22 *
23 * WHERE THE ROWS OF A COME FROM, in the order the simulation engines test them:
24 * the global job cap, the memory budget weighted by the per-class sizes, the
25 * per-class job caps, and any explicit linear constraint. A row left unbounded
26 * (the -1 sentinel) is DROPPED rather than given a surrogate capacity, because
27 * a large finite surrogate would report a small but non-zero blocking where the
28 * truth is none. A region that declares no bounded row at all has no admission
29 * rule and is refused rather than solved as an unconstrained delay.
30 *
31 * Every row is a function of the per-class occupancy of the REGION only, which
32 * is all the FiniteCapacityRegion API can express, so no row can distinguish
33 * the stations inside the region. That is why a second member station would
34 * change nothing in A and yet would break the Little's-law recovery below,
35 * which attributes the whole carried load to one infinite server; a region with
36 * more than one member station is therefore refused rather than collapsed.
37 *
38 * THE THREE METHODS, ALL PORTED.
39 *
40 * exact / ms `lossn_manjunath`, the Manjunath-Sikdar contour-integral transform,
41 * and the default on integral constraints as in the reference.
42 * EXACT: g(C) is a residue, i.e. a coefficient of a truncated
43 * multivariate power series, so the answer carries neither an
44 * iteration tolerance nor a sampling error. It is the only one of
45 * the three that runs under exact arithmetic, since every step is
46 * rational and the metrics are ratios. Cost is the product of
47 * (C_j+1) over the simultaneously live rows, so it is exact but
48 * not unconditionally cheap.
49 * erlangfp `lossn_erlangfp`, the Erlang fixed-point (reduced-load)
50 * approximation. IT IS AN APPROXIMATION: it assumes the links
51 * block INDEPENDENTLY, which is false whenever two rows of A
52 * share a class, and it is exact only in the single-row case
53 * where the assumption is vacuous and the fixed point collapses
54 * to Erlang's loss formula. Never compare it to an exact solver
55 * at a tight tolerance on a multi-row region.
56 * mci `lossn_mci`, Ross-Wang importance sampling. Unbiased, with a
57 * confidence interval, and it carries a normalizing constant.
58 * The method to reach for when the exact transform's live-grid
59 * product is prohibitive.
60 *
61 * rec `lossn_rec`, MDD-rec: the same constant as the exact sum over
62 * the admissible set, obtained by one memoised walk of the
63 * decision diagram holding it. It places no integrality demand on
64 * A or C, which is why it -- and not 'erlangfp', which this port
65 * refuses there -- is what 'default' takes on a FRACTIONAL region.
66 * Before it existed a fractional region had no exact route here at
67 * all, only the Monte Carlo 'mci' whose answer is a random
68 * variable.
69 *
70 * WHY THE ANALYZER STILL REFUSES 'erlangfp' ON A FRACTIONAL REGION. Since
71 * 2026-10-16 `lossn_erlangfp` is the Kaufman-Roberts fixed point of the
72 * reference and scales a fractional row to integers itself, so the function
73 * accepts such a region; the analyzer keeps refusing it only because
74 * line-test's test_nc_lossn.cpp pins that refusal, and lifting it is a change
75 * of expected behaviour. `lossn_mci` compares in real arithmetic and `lossn_rec`
76 * is exact there, so both remain the methods for a fractional region.
77 * `lossn_manjunath` needs integral rows for a different reason -- the residue argument
78 * counts whole units of capacity -- so an EXPLICIT 'exact' on a fractional
79 * region is refused by `lossn_manjunath` itself rather than downgraded here, exactly
80 * as the reference does; only 'default' falls back.
81 *
82 * ARITHMETIC. There is no blanket transcendental gate: under exact arithmetic
83 * the 'exact' method answers exactly and the other two refuse by name, which is
84 * strictly more useful than refusing the whole analyzer. `lG` is a double in
85 * every arithmetic, because it is a logarithm.
86 */
87
88#include <cctype>
89#include <cmath>
90#include <cstddef>
91#include <cstdint>
92#include <limits>
93#include <string>
94#include <vector>
95
96#include "line/api/da/da_fpi.h"
103#include "line/util/error.h"
104#include "line/util/matrix.h"
105
106namespace line {
107namespace nc {
108
109/** What the loss-network analyzer returns beyond the usual metric table. */
110template <class T>
113 Matrix<T> A; ///< (J x K) rows of the admission rule A n <= C
114 std::vector<T> Cvec; ///< (J) the right-hand side
115 std::vector<T> nu; ///< (K) offered load per class
116 std::vector<T> Loss; ///< (K) blocking probability per class
117 Matrix<T> E; ///< (J x K) blocking of class k on row j, empty outside the fixed point
118};
119
120namespace detail {
121
122/**
123 * The 1-based stations of region `f`.
124 *
125 * `members` is the authoritative flag and is read first. A struct assembled
126 * without it -- which is what MATLAB's `sn.region{f}` is, a capacity matrix and
127 * nothing else -- is read back the reference's way instead, from the -1
128 * sentinel: a station whose row carries any bounded entry is a member. The two
129 * agree on every region the builder produces; they differ only on a member
130 * station left wholly unbounded, which the sentinel test cannot see.
131 */
132template <class T>
133std::vector<std::size_t> lossn_region_members(const qn::NetworkStruct<T>& sn, std::size_t f) {
134 const typename qn::NetworkStruct<T>::Region& rg = sn.regions[f];
135 bool anyFlag = false;
136 for (bool b : rg.members)
137 if (b) anyFlag = true;
138
139 std::vector<std::size_t> out;
140 for (std::size_t i = 0; i < sn.nstations; ++i) {
141 bool in = false;
142 if (anyFlag) {
143 in = i < rg.members.size() && rg.members[i];
144 } else if (i < rg.cap.size()) {
145 for (double c : rg.cap[i])
146 if (c >= 0.0) in = true;
147 if (i < rg.maxmem.size() && rg.maxmem[i] >= 0.0) in = true;
148 }
149 if (in) out.push_back(i + 1);
150 }
151 return out;
152}
153
154/**
155 * Port of the local `lossn_region_constraints` of the reference: the rows of
156 * A n <= C for region `f` at member station `st` (1-based).
157 */
158template <class T>
159void lossn_region_constraints(const qn::NetworkStruct<T>& sn, std::size_t f, std::size_t st,
160 std::size_t K, Matrix<T>& A, std::vector<T>& Cvec) {
161 const typename qn::NetworkStruct<T>::Region& rg = sn.regions[f];
162 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
163 const std::vector<double>& caps = rg.cap[st - 1];
164
165 std::vector<std::vector<T>> rows;
166 std::vector<T> rhs;
167
168 // Global job cap: sum_r n_r <= globalMaxJobs.
169 if (K < caps.size() && caps[K] >= 0.0) {
170 rows.push_back(std::vector<T>(K, one));
171 rhs.push_back(num_traits<T>::from_double(caps[K]));
172 }
173
174 // Memory budget: sum_r size_r n_r <= globalMaxMemory. The class sizes are
175 // the row, so this is the one row that can legitimately be fractional.
176 if (st - 1 < rg.maxmem.size() && rg.maxmem[st - 1] >= 0.0) {
177 std::vector<T> row(K, one);
178 for (std::size_t r = 0; r < K && r < rg.size.size(); ++r) row[r] = rg.size[r];
179 rows.push_back(row);
180 rhs.push_back(num_traits<T>::from_double(rg.maxmem[st - 1]));
181 }
182
183 // Per-class job caps, already folded with the per-class memory caps by
184 // `add_region`, so there is no separate per-class memory row.
185 for (std::size_t r = 0; r < K && r < caps.size(); ++r) {
186 if (caps[r] < 0.0) continue;
187 std::vector<T> row(K, zero);
188 row[r] = one;
189 rows.push_back(row);
190 rhs.push_back(num_traits<T>::from_double(caps[r]));
191 }
192
193 // Explicit linear constraints from FiniteCapacityRegion.setConstraint.
194 for (std::size_t k = 0; k < rg.lincon_A.rows(); ++k) {
195 std::vector<T> row(K, zero);
196 for (std::size_t r = 0; r < K && r < rg.lincon_A.cols(); ++r) row[r] = rg.lincon_A(k, r);
197 rows.push_back(row);
198 rhs.push_back(k < rg.lincon_b.size() ? rg.lincon_b[k] : zero);
199 }
200
201 if (rows.empty())
202 throw UnsupportedError(ErrorKind{"Region"},
203 "solver_nc_lossn_analyzer: the finite capacity region declares no bounded constraint, "
204 "so it admits every arrival and is not a loss network; give it a global job cap, a "
205 "memory budget, a per-class cap or an explicit linear constraint");
206
207 A = Matrix<T>(rows.size(), K, zero);
208 for (std::size_t j = 0; j < rows.size(); ++j)
209 for (std::size_t r = 0; r < K; ++r) A(j, r) = rows[j][r];
210 Cvec = rhs;
211}
212
213/** `options.method` split on '.' and '/' and lowercased, as `nc` does elsewhere. */
214inline std::vector<std::string> lossn_tokens(const std::string& method) {
215 std::vector<std::string> toks;
216 std::string tok;
217 for (char ch : method) {
218 if (ch == '.' || ch == '/') {
219 toks.push_back(tok);
220 tok.clear();
221 } else {
222 tok += static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
223 }
224 }
225 toks.push_back(tok);
226 return toks;
227}
228
229inline bool lossn_has_token(const std::vector<std::string>& toks, const char* what) {
230 for (const std::string& t : toks)
231 if (t == what) return true;
232 return false;
233}
234
235} // namespace detail
236
237/**
238 * True when the model has the SHAPE of a loss network -- open, one region, one
239 * member station, that station an infinite server -- whatever admission rule
240 * the region applies.
241 *
242 * SEPARATE FROM THE RULE TEST ON PURPOSE. A shape test that also demanded DROP
243 * would make a WAITQ region on this very shape indistinguishable, to the
244 * caller, from a model with no region at all, and the runner would then answer
245 * it as an unconstrained network. The reference splits the two questions the
246 * same way: `runAnalyzer.m:307-327` first recognises the shape, and only then
247 * branches on the rule, raising on WAITQ rather than falling through.
248 *
249 * The member test is `isinf(nservers)`, not `nodetype == Delay`, to match the
250 * reference. An infinite-server Queue has the shape and must reach the analyzer
251 * so its by-name refusal fires; excluding it here would silently hide it.
252 */
253template <class T>
255 if (sn.regions.size() != 1) return false;
256 const std::vector<std::size_t> mem = detail::lossn_region_members(sn, 0);
257 if (mem.size() != 1) return false;
258 const std::size_t st = mem[0];
259 if (st == 0 || st > sn.stations.size()) return false;
260 if (std::isfinite(sn.stations[st - 1].nservers)) return false;
261
262 for (const qn::JobClass& c : sn.classes)
263 if (std::isfinite(c.population) && c.population > 0.0) return false;
264 return true;
265}
266
267/**
268 * True when the model is a loss network: the shape above, with EVERY class
269 * dropped at the region.
270 *
271 * The DROP rule is what makes it a LOSS network rather than a blocking one. A
272 * WAITQ region holds the arrival back instead of discarding it, which is a
273 * queueing phenomenon the Erlang model has no state for, so it must not be
274 * routed here.
275 *
276 * ALL classes, not merely one: the reference tests `all(regionrule(1,:) ==
277 * DROP)`. A region that discards one class and holds another back is a mixed
278 * system whose blocked class occupies the region while it waits, so the
279 * per-class loss probabilities the Erlang fixed point returns would not be the
280 * ones the model implies.
281 */
282template <class T>
284 if (!nc_has_lossn_shape(sn)) return false;
285 if (sn.regions[0].rule.size() < sn.nclasses) return false;
286 for (std::size_t r = 0; r < sn.nclasses; ++r)
287 if (sn.regions[0].rule[r] != lang::DropStrategy::DROP) return false;
288 return true;
289}
290
291/**
292 * Port of `solver_nc_lossn_analyzer.m`.
293 *
294 * @param sn the refreshed struct; must satisfy `nc_is_lossn_model`
295 * @param opt solver controls; `method` selects erlangfp / mci
296 */
297template <class T>
299 const NcSolverOptions& opt) {
301 {
302 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
303 const std::size_t K = sn.nclasses, M = sn.nstations;
304
305 // 1. The delay inside the region.
306 const std::vector<std::size_t> mem = detail::lossn_region_members(sn, 0);
307 if (sn.regions.empty())
308 throw UnsupportedError(ErrorKind{"Topology"},
309 "solver_nc_lossn_analyzer: the model declares no finite capacity region, so it is "
310 "not a loss network");
311 if (mem.size() != 1)
312 throw UnsupportedError(ErrorKind{"Region"},
313 "solver_nc_lossn_analyzer: the finite capacity region holds " +
314 std::to_string(mem.size()) +
315 " stations; the admission rule can only see the occupancy of the region as a "
316 "whole, so the carried load could not be attributed to a station");
317 const std::size_t delayIdx = mem[0];
318 if (sn.stations[delayIdx - 1].nodetype != qn::NodeType::Delay)
319 throw UnsupportedError(ErrorKind{"Region"},
320 "solver_nc_lossn_analyzer: the station inside the finite capacity region is not a "
321 "Delay; a loss network holds admitted jobs at an infinite server, and a queueing "
322 "station would make the response time depend on the population");
323
324 // 2. Offered load. A route carries nu_r = arrival rate times mean
325 // holding time INSIDE the region, i.e. the visit ratio at the delay over
326 // its service rate. The bare arrival rate would be right only for unit
327 // mean service times.
328 out.nu.assign(K, zero);
329 std::vector<T> lambda(K, zero), mu(K, zero);
330 const std::size_t dsf = sn.stateful_of_station(delayIdx);
331 for (std::size_t r = 0; r < K; ++r) {
332 const std::size_t src = sn.classes[r].refstat;
333 if (src == 0 || src > M) continue;
334 if (!sn.disabled[src - 1][r]) lambda[r] = sn.rates(src - 1, r);
335 // A class the delay never serves never enters the region: it offers
336 // no load and is reported blocked with probability zero, which is
337 // what a rate read out of a disabled pair could not express.
338 if (sn.disabled[delayIdx - 1][r]) continue;
339 mu[r] = sn.rates(delayIdx - 1, r);
340 if (mu[r] == zero)
341 throw UnsupportedError(ErrorKind{"Region"},
342 "solver_nc_lossn_analyzer: class " + std::to_string(r + 1) +
343 " is enabled at the delay with a zero service rate, so its mean holding time "
344 "in the region is unbounded and its offered load is undefined");
345
346 T V = one;
347 const std::size_t rsf = sn.stateful_of_station(src);
348 for (std::size_t c = 0; c < sn.nchains; ++c) {
349 if (!sn.chains[c][r]) continue;
350 const T vref = sn.visits[c](rsf - 1, r);
351 if (vref > zero) V = T(sn.visits[c](dsf - 1, r) / vref);
352 break;
353 }
354 out.nu[r] = T(lambda[r] * V / mu[r]);
355 }
356
357 // 3. The admission rule.
358 detail::lossn_region_constraints(sn, 0, delayIdx, K, out.A, out.Cvec);
359 const std::size_t J = out.Cvec.size();
360
361 // 4. Method selection, following the reference's precedence: an explicit
362 // 'mci' wins, then an explicit 'erlangfp', then the exact transform.
363 const std::vector<std::string> toks = detail::lossn_tokens(opt.method);
364 bool integral = true;
365 for (std::size_t j = 0; j < J; ++j) {
366 const double c = num_traits<T>::to_double(out.Cvec[j]);
367 if (std::fabs(c - std::round(c)) > 1e-9) integral = false;
368 for (std::size_t r = 0; r < K; ++r) {
369 const double a = num_traits<T>::to_double(out.A(j, r));
370 if (std::fabs(a - std::round(a)) > 1e-9) integral = false;
371 }
372 }
373
374 std::string chosen;
375 if (detail::lossn_has_token(toks, "mci")) {
376 chosen = "mci";
377 } else if (detail::lossn_has_token(toks, "erlangfp")) {
378 chosen = "erlangfp";
379 } else if (detail::lossn_has_token(toks, "rec")) {
380 chosen = "rec";
381 } else if (detail::lossn_has_token(toks, "exact") ||
382 detail::lossn_has_token(toks, "manjunath") ||
383 detail::lossn_has_token(toks, "ms")) {
384 // An EXPLICIT request for the transform is honoured even on a
385 // fractional region, where `lossn_manjunath` refuses it by name; silently
386 // answering with the Erlang approximation instead would report an
387 // approximation under the name of an exact method.
388 chosen = "exact";
389 } else {
390 // Only 'default' falls back, and only on a fractional region -- to
391 // MDD-rec, which is exact there, rather than to 'erlangfp', which
392 // this port refuses on a fractional region.
393 chosen = integral ? "exact" : "rec";
394 }
395
396 double lG = std::numeric_limits<double>::quiet_NaN();
397 std::vector<T> QLen(K, zero);
398 out.Loss.assign(K, zero);
399 int niter = 0;
400 std::string method;
401
402 if (chosen == "rec") {
403 const lossn::LossnRecResult<T> rr = lossn::lossn_rec<T>(out.nu, out.A, out.Cvec);
404 QLen = rr.QLen;
405 out.Loss = rr.Loss;
406 lG = rr.lG;
407 niter = rr.iterations;
408 method = "lossn.rec";
409 } else if (chosen == "exact") {
412 QLen = mr.QLen;
413 out.Loss = mr.Loss;
414 lG = mr.lG;
415 // The transform is direct, so the iteration count is 1 by
416 // definition, as in the reference.
417 niter = static_cast<int>(mr.iterations);
418 method = "lossn.exact";
419 } else if (chosen == "mci") {
420 if constexpr (!num_traits<T>::has_transcendental) {
421 throw UnsupportedError(ErrorKind{"Arithmetic"},
422 "solver_nc_lossn_analyzer: the 'mci' method forms its importance weights in "
423 "log space and reports a confidence interval, so it is unavailable under exact "
424 "arithmetic -- and meaningless there in any case, since the estimate is a "
425 "random variable. Use 'exact', the Manjunath-Sikdar transform, which is "
426 "rational throughout");
427 } else {
429 mciopt.samples = opt.samples;
430 const lossn::LossnMciResult<T> mr =
431 lossn::lossn_mci<T>(out.nu, out.A, out.Cvec, mciopt,
432 static_cast<std::uint64_t>(opt.seed));
433 QLen = mr.QLen;
434 out.Loss = mr.Loss;
435 lG = mr.lG;
436 // The sampler does not iterate; the reference reports the
437 // realised sample count in the slot the other methods use for
438 // the iteration count, so a caller can tell how much work
439 // produced the estimate.
440 niter = static_cast<int>(mr.nsamples);
441 method = "lossn.mci";
442 }
443 } else {
444 if constexpr (!num_traits<T>::has_transcendental) {
445 throw UnsupportedError(ErrorKind{"Arithmetic"},
446 "solver_nc_lossn_analyzer: the 'erlangfp' method stops on a tolerance, so it "
447 "is unavailable under "
448 "exact arithmetic and would not be exact there anyway. Use 'exact', the "
449 "Manjunath-Sikdar transform");
450 } else {
451 // lossn_erlangfp scales a fractional row to integers as MATLAB
452 // does, but line-test's test_nc_lossn.cpp still pins this
453 // refusal; lifting it needs that expectation changed by consent.
454 if (!integral)
455 throw UnsupportedError(ErrorKind{"Region"},
456 "solver_nc_lossn_analyzer: the 'erlangfp' method is not offered on a "
457 "region with fractional circuit requirements or capacity (a memory budget "
458 "with fractional class sizes, or an explicit linear constraint). Use "
459 "'mci', which compares in real arithmetic, or 'rec', which is exact there");
460 // The reference hardwires its fixed-point settings inside
461 // lossn_erlangfp rather than reading options.iter_tol.
462 const lossn::ErlangFpResult<T> fr =
464 QLen = fr.QLen;
465 out.Loss = fr.Loss;
466 out.E = fr.E;
467 niter = static_cast<int>(fr.iterations);
468 method = "lossn.erlangfp";
469 }
470 }
471
472 // 5. The metric table. QLen is the CARRIED load E[n_r], so the carried
473 // throughput follows from Little's law at the infinite server.
474 out.sol.sol.Q = Matrix<T>(M, K, zero);
475 out.sol.sol.U = Matrix<T>(M, K, zero);
476 out.sol.sol.R = Matrix<T>(M, K, zero);
477 out.sol.sol.Tp = Matrix<T>(M, K, zero);
478 out.sol.sol.C.assign(K, zero);
479 out.sol.sol.X.assign(K, zero);
480 for (std::size_t r = 0; r < K; ++r) {
481 const std::size_t src = sn.classes[r].refstat;
482 const T Xc = T(lambda[r] * (one - out.Loss[r]));
483 out.sol.sol.X[r] = Xc;
484 out.sol.sol.Tp(delayIdx - 1, r) = Xc;
485 // The source emits the ACCEPTED (post-drop) rate, so that the
486 // routing-based arrival rate `sn_get_arvr_from_tput` derives is
487 // non-zero at the delay and agrees with the flow-conserving
488 // departure throughput and with the rate SolverJMT simulates.
489 if (src >= 1 && src <= M) out.sol.sol.Tp(src - 1, r) = Xc;
490 out.sol.sol.Q(delayIdx - 1, r) = QLen[r];
491 // An infinite server never queues, so the response time is the
492 // service time and the "utilization" is the mean number of busy
493 // servers, which is the population itself.
494 if (mu[r] != zero) out.sol.sol.R(delayIdx - 1, r) = T(one / mu[r]);
495 out.sol.sol.U(delayIdx - 1, r) = QLen[r];
496 }
497 out.sol.sol.lG = lG;
498 out.sol.sol.iter = niter;
499 out.sol.sol.method = method;
500 out.sol.actualmethod = method;
501 return out;
502 }
503}
504
505} // namespace nc
506} // namespace line
507
508#endif // LINE_SOLVERS_NC_SOLVER_NC_LOSSN_H
UnsupportedError(const std::string &what)
Definition error.h:119
A network plus its refreshed NetworkStruct.
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
The exception types the port throws.
Erlang fixed-point (reduced-load) approximation for a loss network.
Exact analysis of a loss network by the Manjunath-Sikdar transform.
Monte Carlo importance-sampling summation for product-form loss networks.
Exact analysis of a loss network by MDD-rec.
Dense matrix and non-owning view.
LossnManjunathResult< T > lossn_manjunath(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< T > &C, const LossnManjunathOptions &options=LossnManjunathOptions())
Exact normalizing constant, carried load and blocking of a loss network.
ErlangFpResult< T > lossn_erlangfp(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< T > &C, const da::FpiOptions &options=lossn_erlangfp_options())
Erlang fixed-point (reduced-load) approximation for a loss network.
LossnMciResult< T > lossn_mci(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< T > &C, const LossnMciOptions< T > &opt, Rng &gen)
Estimate the normalizing constant and the blocking probabilities.
Definition lossn_mci.h:162
LossnRecResult< T > lossn_rec(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< T > &C)
Exact loss-network analysis by MDD-rec.
Definition lossn_rec.h:141
da::FpiOptions lossn_erlangfp_options()
The reference's fixed-point settings: iter_max 10000, iter_tol 1e-10, the max-norm of the increment (...
bool nc_is_lossn_model(const qn::NetworkStruct< T > &sn)
True when the model is a loss network: the shape above, with EVERY class dropped at the region.
bool nc_has_lossn_shape(const qn::NetworkStruct< T > &sn)
True when the model has the SHAPE of a loss network – open, one region, one member station,...
NcLossnSolution< T > solver_nc_lossn_analyzer(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Port of solver_nc_lossn_analyzer.m.
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.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Controls of lossn_manjunath.
Result of lossn_manjunath.
double lG
log of the EXACT normalizing constant g(C)
std::vector< T > QLen
mean carried load E[n_r] per route
std::vector< T > Loss
blocking probability per route
std::size_t iterations
Always 1: the transform is direct, and the field exists for the shared analyzer contract that the ite...
Options of lossn_mci, MATLAB's options struct.
Definition lossn_mci.h:66
Result of lossn_mci.
Definition lossn_mci.h:74
std::vector< T > QLen
mean carried load per route
Definition lossn_mci.h:75
std::vector< T > Loss
blocking probability per route
Definition lossn_mci.h:76
double lG
log of the estimated normalizing constant
Definition lossn_mci.h:77
Carried load, blocking, log normalising constant and walk count.
Definition lossn_rec.h:68
double lG
log G(C), a double diagnostic.
Definition lossn_rec.h:76
std::vector< T > Loss
Blocking probability per class.
Definition lossn_rec.h:72
std::vector< T > QLen
Mean number of class-r calls in progress, the carried load.
Definition lossn_rec.h:70
int iterations
Number of diagram walks performed, K + 1.
Definition lossn_rec.h:78
What the loss-network analyzer returns beyond the usual metric table.
std::vector< T > Loss
(K) blocking probability per class
Matrix< T > E
(J x K) blocking of class k on row j, empty outside the fixed point
Matrix< T > A
(J x K) rows of the admission rule A n <= C
std::vector< T > nu
(K) offered load per class
std::vector< T > Cvec
(J) the right-hand side
The [Q,U,R,T,C,X,lG] of the reference, plus the algorithm that ran.
Definition nc_types.h:113
Controls, defaulting to SolverOptions('NC') in the reference.
Definition nc_types.h:33
One job class of the network.
double population
infinite for an open class
FINITE CAPACITY REGIONS, MATLAB's refreshRegions output.
Matrix< T > lincon_A
optional linear constraint A n <= b
std::vector< std::vector< double > > cap
(nstations x nclasses+1), -1 = unbounded
std::vector< double > maxmem
per member station, -1 = unbounded
std::vector< T > size
per class; size is the memory footprint
std::vector< bool > members
membership, independent of the caps