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 'erlangfp' NEEDS INTEGRAL A AND C AND 'mci' DOES NOT. The ported
71 * `lossn_erlangfp` raises (1-E_i) to the power A(i,r) through `num_pow_int`,
72 * which takes an unsigned exponent, and calls `erlang_b` with an `int`
73 * capacity; a fractional entry would be TRUNCATED silently, solving a different
74 * region. The reference evaluates both through `factln`, i.e. a gamma function,
75 * so it accepts fractional arguments -- which is exactly why its 'default'
76 * falls back to 'erlangfp' when the region declares fractional class sizes. The
77 * port cannot follow that fallback and refuses it by name. `lossn_mci` sums
78 * over an integer lattice of states but compares in real arithmetic, so it has
79 * no such restriction and is the method to use on a fractional region.
80 * `lossn_manjunath` needs integral rows for a different reason -- the residue argument
81 * counts whole units of capacity -- so an EXPLICIT 'exact' on a fractional
82 * region is refused by `lossn_manjunath` itself rather than downgraded here, exactly
83 * as the reference does; only 'default' falls back.
84 *
85 * ARITHMETIC. There is no blanket transcendental gate: under exact arithmetic
86 * the 'exact' method answers exactly and the other two refuse by name, which is
87 * strictly more useful than refusing the whole analyzer. `lG` is a double in
88 * every arithmetic, because it is a logarithm.
89 */
90
91#include <cctype>
92#include <cmath>
93#include <cstddef>
94#include <cstdint>
95#include <limits>
96#include <string>
97#include <vector>
98
99#include "line/api/da/da_fpi.h"
106#include "line/util/error.h"
107#include "line/util/matrix.h"
108
109namespace line {
110namespace nc {
111
112/** What the loss-network analyzer returns beyond the usual metric table. */
113template <class T>
116 Matrix<T> A; ///< (J x K) rows of the admission rule A n <= C
117 std::vector<T> Cvec; ///< (J) the right-hand side
118 std::vector<T> nu; ///< (K) offered load per class
119 std::vector<T> Loss; ///< (K) blocking probability per class
120 std::vector<T> E; ///< (J) per-row blocking, empty outside the fixed point
121};
122
123namespace detail {
124
125/**
126 * The 1-based stations of region `f`.
127 *
128 * `members` is the authoritative flag and is read first. A struct assembled
129 * without it -- which is what MATLAB's `sn.region{f}` is, a capacity matrix and
130 * nothing else -- is read back the reference's way instead, from the -1
131 * sentinel: a station whose row carries any bounded entry is a member. The two
132 * agree on every region the builder produces; they differ only on a member
133 * station left wholly unbounded, which the sentinel test cannot see.
134 */
135template <class T>
136std::vector<std::size_t> lossn_region_members(const qn::NetworkStruct<T>& sn, std::size_t f) {
137 const typename qn::NetworkStruct<T>::Region& rg = sn.regions[f];
138 bool anyFlag = false;
139 for (bool b : rg.members)
140 if (b) anyFlag = true;
141
142 std::vector<std::size_t> out;
143 for (std::size_t i = 0; i < sn.nstations; ++i) {
144 bool in = false;
145 if (anyFlag) {
146 in = i < rg.members.size() && rg.members[i];
147 } else if (i < rg.cap.size()) {
148 for (double c : rg.cap[i])
149 if (c >= 0.0) in = true;
150 if (i < rg.maxmem.size() && rg.maxmem[i] >= 0.0) in = true;
151 }
152 if (in) out.push_back(i + 1);
153 }
154 return out;
155}
156
157/**
158 * Port of the local `lossn_region_constraints` of the reference: the rows of
159 * A n <= C for region `f` at member station `st` (1-based).
160 */
161template <class T>
162void lossn_region_constraints(const qn::NetworkStruct<T>& sn, std::size_t f, std::size_t st,
163 std::size_t K, Matrix<T>& A, std::vector<T>& Cvec) {
164 const typename qn::NetworkStruct<T>::Region& rg = sn.regions[f];
165 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
166 const std::vector<double>& caps = rg.cap[st - 1];
167
168 std::vector<std::vector<T>> rows;
169 std::vector<T> rhs;
170
171 // Global job cap: sum_r n_r <= globalMaxJobs.
172 if (K < caps.size() && caps[K] >= 0.0) {
173 rows.push_back(std::vector<T>(K, one));
174 rhs.push_back(num_traits<T>::from_double(caps[K]));
175 }
176
177 // Memory budget: sum_r size_r n_r <= globalMaxMemory. The class sizes are
178 // the row, so this is the one row that can legitimately be fractional.
179 if (st - 1 < rg.maxmem.size() && rg.maxmem[st - 1] >= 0.0) {
180 std::vector<T> row(K, one);
181 for (std::size_t r = 0; r < K && r < rg.size.size(); ++r) row[r] = rg.size[r];
182 rows.push_back(row);
183 rhs.push_back(num_traits<T>::from_double(rg.maxmem[st - 1]));
184 }
185
186 // Per-class job caps, already folded with the per-class memory caps by
187 // `add_region`, so there is no separate per-class memory row.
188 for (std::size_t r = 0; r < K && r < caps.size(); ++r) {
189 if (caps[r] < 0.0) continue;
190 std::vector<T> row(K, zero);
191 row[r] = one;
192 rows.push_back(row);
193 rhs.push_back(num_traits<T>::from_double(caps[r]));
194 }
195
196 // Explicit linear constraints from FiniteCapacityRegion.setConstraint.
197 for (std::size_t k = 0; k < rg.lincon_A.rows(); ++k) {
198 std::vector<T> row(K, zero);
199 for (std::size_t r = 0; r < K && r < rg.lincon_A.cols(); ++r) row[r] = rg.lincon_A(k, r);
200 rows.push_back(row);
201 rhs.push_back(k < rg.lincon_b.size() ? rg.lincon_b[k] : zero);
202 }
203
204 if (rows.empty())
205 throw UnsupportedError(
206 "solver_nc_lossn_analyzer: the finite capacity region declares no bounded constraint, "
207 "so it admits every arrival and is not a loss network; give it a global job cap, a "
208 "memory budget, a per-class cap or an explicit linear constraint");
209
210 A = Matrix<T>(rows.size(), K, zero);
211 for (std::size_t j = 0; j < rows.size(); ++j)
212 for (std::size_t r = 0; r < K; ++r) A(j, r) = rows[j][r];
213 Cvec = rhs;
214}
215
216/** `options.method` split on '.' and '/' and lowercased, as `nc` does elsewhere. */
217inline std::vector<std::string> lossn_tokens(const std::string& method) {
218 std::vector<std::string> toks;
219 std::string tok;
220 for (char ch : method) {
221 if (ch == '.' || ch == '/') {
222 toks.push_back(tok);
223 tok.clear();
224 } else {
225 tok += static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
226 }
227 }
228 toks.push_back(tok);
229 return toks;
230}
231
232inline bool lossn_has_token(const std::vector<std::string>& toks, const char* what) {
233 for (const std::string& t : toks)
234 if (t == what) return true;
235 return false;
236}
237
238} // namespace detail
239
240/**
241 * True when the model has the SHAPE of a loss network -- open, one region, one
242 * member station, that station an infinite server -- whatever admission rule
243 * the region applies.
244 *
245 * SEPARATE FROM THE RULE TEST ON PURPOSE. A shape test that also demanded DROP
246 * would make a WAITQ region on this very shape indistinguishable, to the
247 * caller, from a model with no region at all, and the runner would then answer
248 * it as an unconstrained network. The reference splits the two questions the
249 * same way: `runAnalyzer.m:307-327` first recognises the shape, and only then
250 * branches on the rule, raising on WAITQ rather than falling through.
251 *
252 * The member test is `isinf(nservers)`, not `nodetype == Delay`, to match the
253 * reference. An infinite-server Queue has the shape and must reach the analyzer
254 * so its by-name refusal fires; excluding it here would silently hide it.
255 */
256template <class T>
258 if (sn.regions.size() != 1) return false;
259 const std::vector<std::size_t> mem = detail::lossn_region_members(sn, 0);
260 if (mem.size() != 1) return false;
261 const std::size_t st = mem[0];
262 if (st == 0 || st > sn.stations.size()) return false;
263 if (std::isfinite(sn.stations[st - 1].nservers)) return false;
264
265 for (const qn::JobClass& c : sn.classes)
266 if (std::isfinite(c.population) && c.population > 0.0) return false;
267 return true;
268}
269
270/**
271 * True when the model is a loss network: the shape above, with EVERY class
272 * dropped at the region.
273 *
274 * The DROP rule is what makes it a LOSS network rather than a blocking one. A
275 * WAITQ region holds the arrival back instead of discarding it, which is a
276 * queueing phenomenon the Erlang model has no state for, so it must not be
277 * routed here.
278 *
279 * ALL classes, not merely one: the reference tests `all(regionrule(1,:) ==
280 * DROP)`. A region that discards one class and holds another back is a mixed
281 * system whose blocked class occupies the region while it waits, so the
282 * per-class loss probabilities the Erlang fixed point returns would not be the
283 * ones the model implies.
284 */
285template <class T>
287 if (!nc_has_lossn_shape(sn)) return false;
288 if (sn.regions[0].rule.size() < sn.nclasses) return false;
289 for (std::size_t r = 0; r < sn.nclasses; ++r)
290 if (sn.regions[0].rule[r] != lang::DropStrategy::DROP) return false;
291 return true;
292}
293
294/**
295 * Port of `solver_nc_lossn_analyzer.m`.
296 *
297 * @param sn the refreshed struct; must satisfy `nc_is_lossn_model`
298 * @param opt solver controls; `method` selects erlangfp / mci
299 */
300template <class T>
302 const NcSolverOptions& opt) {
304 {
305 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
306 const std::size_t K = sn.nclasses, M = sn.nstations;
307
308 // 1. The delay inside the region.
309 const std::vector<std::size_t> mem = detail::lossn_region_members(sn, 0);
310 if (sn.regions.empty())
311 throw UnsupportedError(
312 "solver_nc_lossn_analyzer: the model declares no finite capacity region, so it is "
313 "not a loss network");
314 if (mem.size() != 1)
315 throw UnsupportedError(
316 "solver_nc_lossn_analyzer: the finite capacity region holds " +
317 std::to_string(mem.size()) +
318 " stations; the admission rule can only see the occupancy of the region as a "
319 "whole, so the carried load could not be attributed to a station");
320 const std::size_t delayIdx = mem[0];
321 if (sn.stations[delayIdx - 1].nodetype != qn::NodeType::Delay)
322 throw UnsupportedError(
323 "solver_nc_lossn_analyzer: the station inside the finite capacity region is not a "
324 "Delay; a loss network holds admitted jobs at an infinite server, and a queueing "
325 "station would make the response time depend on the population");
326
327 // 2. Offered load. A route carries nu_r = arrival rate times mean
328 // holding time INSIDE the region, i.e. the visit ratio at the delay over
329 // its service rate. The bare arrival rate would be right only for unit
330 // mean service times.
331 out.nu.assign(K, zero);
332 std::vector<T> lambda(K, zero), mu(K, zero);
333 const std::size_t dsf = sn.stateful_of_station(delayIdx);
334 for (std::size_t r = 0; r < K; ++r) {
335 const std::size_t src = sn.classes[r].refstat;
336 if (src == 0 || src > M) continue;
337 if (!sn.disabled[src - 1][r]) lambda[r] = sn.rates(src - 1, r);
338 // A class the delay never serves never enters the region: it offers
339 // no load and is reported blocked with probability zero, which is
340 // what a rate read out of a disabled pair could not express.
341 if (sn.disabled[delayIdx - 1][r]) continue;
342 mu[r] = sn.rates(delayIdx - 1, r);
343 if (mu[r] == zero)
344 throw UnsupportedError(
345 "solver_nc_lossn_analyzer: class " + std::to_string(r + 1) +
346 " is enabled at the delay with a zero service rate, so its mean holding time "
347 "in the region is unbounded and its offered load is undefined");
348
349 T V = one;
350 const std::size_t rsf = sn.stateful_of_station(src);
351 for (std::size_t c = 0; c < sn.nchains; ++c) {
352 if (!sn.chains[c][r]) continue;
353 const T vref = sn.visits[c](rsf - 1, r);
354 if (vref > zero) V = T(sn.visits[c](dsf - 1, r) / vref);
355 break;
356 }
357 out.nu[r] = T(lambda[r] * V / mu[r]);
358 }
359
360 // 3. The admission rule.
361 detail::lossn_region_constraints(sn, 0, delayIdx, K, out.A, out.Cvec);
362 const std::size_t J = out.Cvec.size();
363
364 // 4. Method selection, following the reference's precedence: an explicit
365 // 'mci' wins, then an explicit 'erlangfp', then the exact transform.
366 const std::vector<std::string> toks = detail::lossn_tokens(opt.method);
367 bool integral = true;
368 for (std::size_t j = 0; j < J; ++j) {
369 const double c = num_traits<T>::to_double(out.Cvec[j]);
370 if (std::fabs(c - std::round(c)) > 1e-9) integral = false;
371 for (std::size_t r = 0; r < K; ++r) {
372 const double a = num_traits<T>::to_double(out.A(j, r));
373 if (std::fabs(a - std::round(a)) > 1e-9) integral = false;
374 }
375 }
376
377 std::string chosen;
378 if (detail::lossn_has_token(toks, "mci")) {
379 chosen = "mci";
380 } else if (detail::lossn_has_token(toks, "erlangfp")) {
381 chosen = "erlangfp";
382 } else if (detail::lossn_has_token(toks, "rec")) {
383 chosen = "rec";
384 } else if (detail::lossn_has_token(toks, "exact") ||
385 detail::lossn_has_token(toks, "manjunath") ||
386 detail::lossn_has_token(toks, "ms")) {
387 // An EXPLICIT request for the transform is honoured even on a
388 // fractional region, where `lossn_manjunath` refuses it by name; silently
389 // answering with the Erlang approximation instead would report an
390 // approximation under the name of an exact method.
391 chosen = "exact";
392 } else {
393 // Only 'default' falls back, and only on a fractional region -- to
394 // MDD-rec, which is exact there, rather than to 'erlangfp', which
395 // this port refuses on a fractional region.
396 chosen = integral ? "exact" : "rec";
397 }
398
399 double lG = std::numeric_limits<double>::quiet_NaN();
400 std::vector<T> QLen(K, zero);
401 out.Loss.assign(K, zero);
402 int niter = 0;
403 std::string method;
404
405 if (chosen == "rec") {
406 const lossn::LossnRecResult<T> rr = lossn::lossn_rec<T>(out.nu, out.A, out.Cvec);
407 QLen = rr.QLen;
408 out.Loss = rr.Loss;
409 lG = rr.lG;
410 niter = rr.iterations;
411 method = "lossn.rec";
412 } else if (chosen == "exact") {
415 QLen = mr.QLen;
416 out.Loss = mr.Loss;
417 lG = mr.lG;
418 // The transform is direct, so the iteration count is 1 by
419 // definition, as in the reference.
420 niter = static_cast<int>(mr.iterations);
421 method = "lossn.exact";
422 } else if (chosen == "mci") {
423 if constexpr (!num_traits<T>::has_transcendental) {
424 throw UnsupportedError(
425 "solver_nc_lossn_analyzer: the 'mci' method forms its importance weights in "
426 "log space and reports a confidence interval, so it is unavailable under exact "
427 "arithmetic -- and meaningless there in any case, since the estimate is a "
428 "random variable. Use 'exact', the Manjunath-Sikdar transform, which is "
429 "rational throughout");
430 } else {
432 mciopt.samples = opt.samples;
433 const lossn::LossnMciResult<T> mr =
434 lossn::lossn_mci<T>(out.nu, out.A, out.Cvec, mciopt,
435 static_cast<std::uint64_t>(opt.seed));
436 QLen = mr.QLen;
437 out.Loss = mr.Loss;
438 lG = mr.lG;
439 // The sampler does not iterate; the reference reports the
440 // realised sample count in the slot the other methods use for
441 // the iteration count, so a caller can tell how much work
442 // produced the estimate.
443 niter = static_cast<int>(mr.nsamples);
444 method = "lossn.mci";
445 }
446 } else {
447 if constexpr (!num_traits<T>::has_transcendental) {
448 throw UnsupportedError(
449 "solver_nc_lossn_analyzer: the 'erlangfp' method evaluates Erlang's loss "
450 "formula through logs and stops on a tolerance, so it is unavailable under "
451 "exact arithmetic and would not be exact there anyway. Use 'exact', the "
452 "Manjunath-Sikdar transform");
453 } else {
454 if (!integral)
455 throw UnsupportedError(
456 "solver_nc_lossn_analyzer: the 'erlangfp' method as ported takes integer "
457 "circuit requirements and an integer link capacity, and this region "
458 "declares fractional ones (a memory budget with fractional class sizes, or "
459 "an explicit linear constraint). The reference evaluates Erlang B through "
460 "factln and accepts them; the port would truncate them and solve a "
461 "different region. Use 'mci', which compares in real arithmetic");
462 std::vector<int> Cint(J, 0);
463 for (std::size_t j = 0; j < J; ++j)
464 Cint[j] = static_cast<int>(std::lround(num_traits<T>::to_double(out.Cvec[j])));
465 // The reference hardwires these inside lossn_erlangfp rather
466 // than reading options.iter_tol, and stops on a non-finite
467 // increment, which the shared driver does only when asked.
468 da::FpiOptions fpopt;
469 fpopt.nanstop = true;
470 const lossn::ErlangFpResult<T> fr =
471 lossn::lossn_erlangfp<T>(out.nu, out.A, Cint, fpopt);
472 QLen = fr.QLen;
473 out.Loss = fr.Loss;
474 out.E = fr.E;
475 niter = static_cast<int>(fr.iterations);
476 method = "lossn.erlangfp";
477 }
478 }
479
480 // 5. The metric table. QLen is the CARRIED load E[n_r], so the carried
481 // throughput follows from Little's law at the infinite server.
482 out.sol.sol.Q = Matrix<T>(M, K, zero);
483 out.sol.sol.U = Matrix<T>(M, K, zero);
484 out.sol.sol.R = Matrix<T>(M, K, zero);
485 out.sol.sol.Tp = Matrix<T>(M, K, zero);
486 out.sol.sol.C.assign(K, zero);
487 out.sol.sol.X.assign(K, zero);
488 for (std::size_t r = 0; r < K; ++r) {
489 const std::size_t src = sn.classes[r].refstat;
490 const T Xc = T(lambda[r] * (one - out.Loss[r]));
491 out.sol.sol.X[r] = Xc;
492 out.sol.sol.Tp(delayIdx - 1, r) = Xc;
493 // The source emits the ACCEPTED (post-drop) rate, so that the
494 // routing-based arrival rate `sn_get_arvr_from_tput` derives is
495 // non-zero at the delay and agrees with the flow-conserving
496 // departure throughput and with the rate SolverJMT simulates.
497 if (src >= 1 && src <= M) out.sol.sol.Tp(src - 1, r) = Xc;
498 out.sol.sol.Q(delayIdx - 1, r) = QLen[r];
499 // An infinite server never queues, so the response time is the
500 // service time and the "utilization" is the mean number of busy
501 // servers, which is the population itself.
502 if (mu[r] != zero) out.sol.sol.R(delayIdx - 1, r) = T(one / mu[r]);
503 out.sol.sol.U(delayIdx - 1, r) = QLen[r];
504 }
505 out.sol.sol.lG = lG;
506 out.sol.sol.iter = niter;
507 out.sol.sol.method = method;
508 out.sol.actualmethod = method;
509 return out;
510 }
511}
512
513} // namespace nc
514} // namespace line
515
516#endif // LINE_SOLVERS_NC_SOLVER_NC_LOSSN_H
UnsupportedError(const std::string &what)
Definition error.h:51
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.
ErlangFpResult< T > lossn_erlangfp(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< int > &C, const da::FpiOptions &options=da::FpiOptions())
Erlang fixed-point (reduced-load) approximation for a loss network.
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.
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
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.
Controls and result shape shared by the normalizing-constant analyzers.
A queueing network and its refreshed NetworkStruct.
Options mirroring the fields MATLAB reads off the options struct.
Definition da_fpi.h:50
bool nanstop
stop when the increment norm is not finite
Definition da_fpi.h:55
std::vector< T > E
per-link blocking probabilities (the fixed point)
std::vector< T > QLen
carried traffic per class
std::vector< T > Loss
blocking probability per class
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 > 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
std::vector< T > E
(J) per-row blocking, empty outside the fixed point
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