LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mam_basic_mmap.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_MAM_SOLVER_MAM_BASIC_MMAP_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_BASIC_MMAP_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_mam_basic_mmap.m`, `solver_mam_basic_mmap_inner.m` and
12 * `solver_mam_basic_mmap_closed.m`: the MMAP fork-join decomposition, reached as
13 * the `dec.source.mmap` method and as branch 2b of the dispatch (every open
14 * fork-join model that is not in the homogeneous class `solver_mam_fj` serves).
15 *
16 * THE METHOD. It is a PARAMETRIC DECOMPOSITION, not a per-station isolation:
17 * unlike `dec.source`, which hands every station a rescaled copy of the chain's
18 * source process, this one carries a per-node DEPARTURE process table and
19 * recomputes the arrival stream at every node from the traffic equations each
20 * sweep (`solver_mam_traffic_mmap`, the fork-join aware traffic step). The
21 * departure process of an FCFS or PS station is the ETAQA truncation of its own
22 * QBD (`qbd_depproc_etaqa`, `qbd_depproc_etaqa_ps`), so the correlation a queue
23 * introduces travels downstream instead of being discarded. The fixed point is
24 * driven on the station queue lengths by `da_fpi` with a RELATIVE increment
25 * norm, and the reference starts testing convergence only from the third sweep
26 * (`config.da_miniter = 3`).
27 *
28 * WHAT MAKES IT THE FORK-JOIN ANALYZER. Two things, and neither is in
29 * `dec.source`:
30 * - the traffic step SYNCHRONIZES the flows arriving at a join along one sync
31 * group with `mmap_max` rather than superposing them, so the join's output
32 * process is the slowest branch's, blocking included;
33 * - the join's own metrics are derived AFTER the fixed point from the branch
34 * response times, as the expected maximum of independent exponentials with
35 * rates 1/R_b minus their mean. That difference is the synchronization delay,
36 * and `QN = (sum of branch throughputs) * delay` is Little's law on it.
37 *
38 * THE CLOSED WRAPPER has no source to fix the arrival rates, so it wraps the
39 * inner analyzer in a per-class BISECTION on a surrogate arrival rate, driven
40 * against the population, exactly as `solver_mna_closed` does. Three details of
41 * the reference are reproduced rather than tidied: the bracket's upper bound is
42 * the SLOWEST rate over the finite-server stations (the fallback to the
43 * infinite-server ones, and then to 1, is the reference's own); the loop breaks
44 * when every bracket has collapsed below the precision floor, undoing its own
45 * iteration count as it does so; and a diverged inner call is caught, treated as
46 * an overload, and the last successful metrics are restored if the FINAL trial
47 * is the one that diverged.
48 *
49 * SELF-LOOPING CLASSES. `sn.isslc` guards the surrogate-rate zeroing, the queue
50 * clamp and the final throughput pin in the reference's closed wrapper, and the
51 * PS denominator and the FCFS saturation test in the inner analyzer. The C++
52 * `JobClassType` is OPEN or CLOSED only, so no model this port can build enters
53 * them, and they are not transcribed -- the same decision `solver_mna.h` and
54 * `solver_mam_ag.h` record.
55 *
56 * THE REFERENCE'S OWN QUIRKS, REPRODUCED. `XN` is initialised to zeros and never
57 * assigned by either the inner analyzer or the closed wrapper, so the per-class
58 * throughput column is zero however the model is solved; the station
59 * throughputs in `TN` are the real ones. The inner analyzer's `try/catch` around
60 * the ETAQA departure process, and the closed wrapper's around the whole inner
61 * call, are the reference's control flow and not defensive additions: the first
62 * falls back to the scaled service process, the second to a bisection step
63 * downwards.
64 *
65 * ARITHMETIC. Double (or real) only, for the reasons `solver_mam_basic.h` lists:
66 * the fixed point stops on a tolerance, MMAP[K]/PH[K]/1 runs the ADDA doubling
67 * iteration, and both ETAQA departure processes static_assert on transcendental
68 * arithmetic.
69 */
70
71#include <algorithm>
72#include <cmath>
73#include <limits>
74#include <map>
75#include <string>
76#include <utility>
77#include <vector>
78
79#include "line/api/da/da_fpi.h"
94#include "line/solvers/mam/solver_mam_bmap.h" // mam_detect_mmck
96#include "line/util/error.h"
97#include "line/util/matrix.h"
98
99namespace line {
100namespace mam {
101
102/**
103 * The `options.config` fields the MMAP decomposition reads on top of
104 * `MamOptions`.
105 *
106 * Neither is a SolverMAM option: `etaqa_trunc` is defaulted by
107 * `solver_mam_analyzer.m` before the dispatch and `fj_sync_q_len` by
108 * `solver_mam_traffic_mmap.m` itself, so a caller cannot reach either through
109 * the solver's option surface. Kept out of `MamOptions` for that reason, as
110 * `MnaConfig` is.
111 */
113 /** `config.etaqa_trunc`, the ETAQA level truncation of the departure process. */
114 std::size_t etaqa_trunc = 8;
115 /** `config.fj_sync_q_len`, the synchronization queue at a join. */
116 std::size_t fj_sync_q_len = 2;
117};
118
119namespace basic_mmap_detail {
120
123
124/**
125 * The `PH`, `pie` and `D0` tables both MMAP analyzers build before the loop.
126 *
127 * `PH[i][r]` is the service process of class r at station i AFTER the
128 * reference's per-discipline rescaling (divided by the server count at FCFS,
129 * HOL and PS, left alone at INF and at a Source, whose entry is the ARRIVAL
130 * process the reference never overwrites), `svc[i][r]` is the same law as the
131 * (sigma, S) pair `MMAPPH1FCFS` consumes, and `known[i][r]` says whether the
132 * station serves the class at all.
133 *
134 * The NaN guard below is applied at EVERY station, where the reference applies
135 * it only inside its four rescaling branches. The difference is invisible: an
136 * entry it touches is one whose class the station never serves, whose
137 * throughput is therefore zero, so the reference's NaN and this port's mean of
138 * 1e8 both leave the product at zero -- the first through the terminal NaN
139 * sweep, the second directly.
140 *
141 * THE REFERENCE'S NaN GUARD IS INCONSISTENT, AND IS REPRODUCED AS WRITTEN. When
142 * `D0` comes back NaN -- the class is not served here -- it sets
143 * `D0 = -GlobalConstants.Immediate` (an IMMEDIATE service, rate 1e8) but
144 * `PH = map_exponential(GlobalConstants.Immediate)`, and `map_exponential` takes
145 * a MEAN, so that is a service of mean 1e8, the exact opposite. The two are read
146 * in different places -- `D0`/`pie` by the queue solver, `PH` only through
147 * `map_mean` in the utilization and surrogate-delay lines -- and every metric
148 * that reads `PH` multiplies it by a throughput that is identically zero on such
149 * a class, so the contradiction never reaches a reported number. Written the
150 * reference's way rather than tidied, because tidying it would change what
151 * MMAPPH1FCFS is handed.
152 */
153template <class T>
154struct MmapPhTable {
155 std::vector<std::vector<Map<T>>> PH;
156 std::vector<std::vector<PhService<T>>> svc;
157 std::vector<std::vector<bool>> known;
158};
159
160template <class T>
161MmapPhTable<T> mmap_ph_table(const qn::NetworkStruct<T>& L) {
162 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
163 const std::size_t M = L.nstations, K = L.nclasses;
164 const T imm = num_traits<T>::from_double(GlobalConstants::Immediate);
165 MmapPhTable<T> t;
166 t.PH.assign(M, std::vector<Map<T>>(K));
167 t.svc.assign(M, std::vector<PhService<T>>(K));
168 t.known.assign(M, std::vector<bool>(K, false));
169 for (std::size_t i = 0; i < M; ++i) {
170 const SchedStrategy sc = L.stations[i].sched;
171 // EVERY STATION IS FILLED, not only the ones with a branch. The
172 // reference's `PH = sn.proc` starts as the whole table and its station
173 // loop only OVERWRITES the entries it rescales, so a Source keeps its
174 // own ARRIVAL process there -- and both analyzers read it: `dec.mmap`'s
175 // trailing surrogate-delay block runs at every station, and the MMAP
176 // variant seeds DEP from PH at every station node.
177 // FCFS, HOL and PS are the disciplines the reference divides by the
178 // server count; INF is not (an infinite server has no queue to speed
179 // up), and neither is a Source.
180 const bool divide = (sc == SchedStrategy::FCFS || sc == SchedStrategy::HOL ||
181 sc == SchedStrategy::PS);
182 const double ns = L.stations[i].nservers;
183 for (std::size_t r = 0; r < K; ++r) {
184 if (!L.has_service_law(i, r) || !(L.rates(i, r) > zero)) {
185 t.PH[i][r] = map_exponential_mean(imm); // mean 1e8; see the struct note
186 t.svc[i][r].sigma.assign(1, one);
187 t.svc[i][r].S = Matrix<T>(1, 1, T(-imm));
188 continue;
189 }
190 Map<T> ph = lang::dist_to_map(L.service[i][r]);
191 if (divide && std::isfinite(ns) && ns > 0.0)
192 ph = map_scale(ph, T(map_mean(ph) / num_traits<T>::from_double(ns)));
193 t.PH[i][r] = ph;
194 t.svc[i][r].sigma = map_pie(ph);
195 t.svc[i][r].S = ph.D0;
196 t.known[i][r] = true;
197 }
198 }
199 return t;
200}
201
202/**
203 * The expected maximum of independent exponentials, by inclusion-exclusion.
204 *
205 * The reference writes it as
206 * sum_{p=0}^{n-1} (-1)^p sum(1 ./ sum(nchoosek(lambda, p+1), 2))
207 * i.e. the alternating sum over subsets of every size, which is the classical
208 * E[max] = sum_{S nonempty} (-1)^(|S|+1) / sum_{i in S} lambda_i. Enumerated
209 * here over bitmasks, which is the same set of subsets in the same signs; the
210 * cost is 2^n either way, and the reference's own nchoosek is what bounds n in
211 * practice. A join with more than 20 synchronized branches is refused by name
212 * rather than run into a 10^6-term sum.
213 */
214template <class T>
215T exp_max_mean(const std::vector<T>& rate) {
216 const std::size_t n = rate.size();
217 if (n > 20)
218 throw UnsupportedError(ErrorKind{"Fork"},
219 "solver_mam_basic_mmap: the join synchronizes " + std::to_string(n) +
220 " branches, and the reference's expected-maximum formula is an alternating sum over "
221 "all 2^n subsets of them; that is not evaluable at this width");
222 const T zero = num_traits<T>::from_int(0);
223 T acc = zero;
224 for (std::size_t mask = 1; mask < (std::size_t(1) << n); ++mask) {
225 T s = zero;
226 std::size_t bits = 0;
227 for (std::size_t i = 0; i < n; ++i)
228 if (mask & (std::size_t(1) << i)) {
229 s += rate[i];
230 ++bits;
231 }
232 if (!(s > zero)) continue;
233 const T term = T(num_traits<T>::from_int(1) / s);
234 if (bits % 2 == 1) acc += term;
235 else acc -= term;
236 }
237 return acc;
238}
239
240/** `mmap_compress(ARV{ind}, config)` under the arithmetic gate the tree uses. */
241template <class T>
242Mmap<T> compress_arrival(const Mmap<T>& m) {
243 if constexpr (num_traits<T>::has_transcendental) {
245 } else {
246 (void)m;
247 throw UnsupportedError(ErrorKind{"Arithmetic"},
248 "solver_mam_basic_mmap: the arrival superposition passed config.space_max and must be "
249 "compressed, which fits an APH(2) and needs transcendental arithmetic");
250 }
251}
252
253} // namespace basic_mmap_detail
254
255/**
256 * Port of `solver_mam_basic_mmap_inner.m`.
257 *
258 * @param L the refreshed struct
259 * @param opt the SolverMAM options; `tol` is the fixed point's iter_tol
260 * @param cfg the two `options.config` fields the analyzer defaults itself
261 * @param lambda per-CLASS surrogate arrival rate, the reference's `lambda`
262 * @param totiter out: the sweeps the fixed point took
263 */
264template <class T>
266 const MamOptions& opt, const MmapDecConfig& cfg,
267 const std::vector<T>& lambda,
268 std::size_t* totiter) {
269 if constexpr (!num_traits<T>::has_transcendental) {
270 (void)L; (void)opt; (void)cfg; (void)lambda; (void)totiter;
271 throw UnsupportedError(ErrorKind{"Arithmetic"},
272 "solver_mam_basic_mmap_inner: the departure-process fixed point stops on a tolerance, "
273 "MMAP[K]/PH[K]/1 runs the ADDA doubling iteration and the ETAQA departure process "
274 "needs transcendental arithmetic; rerun with --arith double or --arith real");
275 } else {
276 using namespace basic_mmap_detail;
277 using basic_detail::station_visits;
278 using basic_detail::truncate_renorm;
279 using basic_detail::zero_nans;
280 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
281 const std::size_t I = L.nof_nodes(), M = L.nstations, K = L.nclasses;
282 const T ftol = num_traits<T>::from_double(GlobalConstants::FineTol);
283
284 if (lambda.size() != K)
285 throw InputError("solver_mam_basic_mmap_inner: lambda is not indexed over the classes");
286
287 const Matrix<T> V = station_visits(L);
288 Matrix<T> S(M, K, zero);
289 for (std::size_t i = 0; i < M; ++i)
290 for (std::size_t r = 0; r < K; ++r)
291 if (!L.disabled[i][r] && L.rates(i, r) > zero) S(i, r) = T(one / L.rates(i, r));
292
293 const MmapPhTable<T> ph = mmap_ph_table(L);
296 tcfg.fj_sync_q_len = cfg.fj_sync_q_len;
297
298 Matrix<T> QN(M, K, zero), UN(M, K, zero), RN(M, K, zero), TN(M, K, zero);
299 // The Source's throughput is its declared rate and is set once, before the
300 // loop, exactly as the reference's pre-loop station switch does.
301 for (std::size_t i = 0; i < M; ++i)
302 if (L.stations[i].sched == SchedStrategy::EXT)
303 for (std::size_t r = 0; r < K; ++r)
304 TN(i, r) = L.disabled[i][r] ? zero : L.rates(i, r);
305
306 DepTable<T> DEP(I, std::vector<Map<T>>(K));
307 std::vector<bool> car_warned(M, false); // one carried-phase warning per station
308
309 auto sweep = [&](const std::vector<T>&,
310 std::size_t itnum) -> std::pair<std::vector<T>, std::vector<T>> {
311 // ---- the departure table, NODE-indexed --------------------------
312 if (itnum == 1) {
313 for (std::size_t ind = 0; ind < I; ++ind) {
314 const qn::NodeType ty = L.nodes[ind].nodetype;
315 const bool isfj = (ty == qn::NodeType::Fork || ty == qn::NodeType::Join);
316 if (L.nodes[ind].station != 0 && !isfj) {
317 const std::size_t ist = L.nodes[ind].station - 1;
318 for (std::size_t r = 0; r < K; ++r) {
319 if (V(ist, r) > zero && lambda[r] > zero)
320 DEP[ind][r] =
321 map_scale(ph.PH[ist][r], T(one / T(lambda[r] * V(ist, r))));
322 else
323 DEP[ind][r] = ph.PH[ist][r];
324 }
325 } else {
326 for (std::size_t r = 0; r < K; ++r)
327 DEP[ind][r] = map_exponential_mean(
328 lambda[r] > zero
329 ? T(one / lambda[r])
330 : T(one / num_traits<T>::from_double(GlobalConstants::Immediate)));
331 }
332 }
333 }
334
335 std::vector<Mmap<T>> ARV = solver_mam_traffic_mmap(L, DEP, tcfg, fj);
336
337 std::vector<T> xref(M * K, zero);
338 for (std::size_t i = 0; i < M; ++i)
339 for (std::size_t r = 0; r < K; ++r) xref[i * K + r] = QN(i, r);
340
341 // ---- one isolated-station solve per station ----------------------
342 for (std::size_t i = 0; i < M; ++i) {
343 const std::size_t ind = L.node_of_station(i + 1) - 1;
344 const qn::NodeType ty = L.nodes[ind].nodetype;
345 const SchedStrategy sc = L.stations[i].sched;
346 const double ns = L.stations[i].nservers;
347
348 if (ty == qn::NodeType::Join) {
349 // Zeroed here and rebuilt from the branch response times after
350 // the fixed point; the throughput is the surrogate rate.
351 for (std::size_t r = 0; r < K; ++r) {
352 TN(i, r) = lambda[r];
353 UN(i, r) = zero;
354 QN(i, r) = zero;
355 RN(i, r) = zero;
356 }
357 continue;
358 }
359 if (ty != qn::NodeType::Queue) {
360 if (sc == SchedStrategy::INF) {
361 if (ARV[ind].order() > 0) {
362 const std::vector<T> lam = mmap_lambda(ARV[ind]);
363 for (std::size_t r = 0; r < K; ++r) TN(i, r) = lam[r];
364 }
365 for (std::size_t r = 0; r < K; ++r)
366 if (TN(i, r) > zero) {
367 UN(i, r) = T(S(i, r) * TN(i, r));
368 QN(i, r) = T(TN(i, r) * S(i, r));
369 RN(i, r) = S(i, r);
370 }
371 }
372 // EXT: the throughput was pinned before the loop.
373 continue;
374 }
375 if (ARV[ind].order() == 0) continue;
376 if (ARV[ind].order() > tcfg.space_max) ARV[ind] = compress_arrival(ARV[ind]);
377
378 bool finiteCapUsed = false, exactStation = false;
379 std::vector<PhService<T>> sl;
380 for (std::size_t r = 0; r < K; ++r) sl.push_back(ph.svc[i][r]);
381
382 if (sc == SchedStrategy::FCFS || sc == SchedStrategy::HOL) {
383 if (std::isfinite(L.stations[i].cap)) {
384 const std::size_t capK =
385 static_cast<std::size_t>(std::llround(L.stations[i].cap));
386 T meanQ = zero, lossProb = zero;
387 const MmckDetection<T> det = mam_detect_mmck(L, i + 1, ARV[ind]);
388 if (det.isMmck) {
389 const std::vector<T> lam = mmap_lambda(ARV[ind]);
390 T lamTot = zero;
391 for (const T& v : lam)
392 if (!std::isnan(num_traits<T>::to_double(v))) lamTot += v;
394 lamTot, det.muRate, static_cast<unsigned>(std::llround(ns)),
395 static_cast<unsigned>(capK));
396 meanQ = ex.meanQueueLength;
397 lossProb = ex.lossProbability;
398 } else {
399 const basic_detail::TruncRenorm<T> tr = truncate_renorm(ARV[ind], sl, capK);
400 meanQ = tr.meanQ;
401 lossProb = tr.lossProb;
402 }
403 const std::vector<T> lam = mmap_lambda(ARV[ind]);
404 std::vector<T> eff(K, zero), Sact(K, zero);
405 T sumTN = zero;
406 for (std::size_t r = 0; r < K; ++r) {
407 const T inflow =
408 std::isnan(num_traits<T>::to_double(lam[r])) ? zero : lam[r];
409 eff[r] = T(inflow * T(one - lossProb));
410 // The PH was divided by the server count, so the actual
411 // per-class service mean multiplies it back.
412 Sact[r] = T(map_mean(ph.PH[i][r]) * num_traits<T>::from_double(ns));
413 sumTN += eff[r];
414 }
415 T Wq = zero;
416 if (sumTN > zero) {
417 T sw = zero;
418 for (std::size_t r = 0; r < K; ++r) {
419 const T c = T(eff[r] * Sact[r]);
420 if (!std::isnan(num_traits<T>::to_double(c))) sw += c;
421 }
422 const T w = T(T(meanQ / sumTN) - T(sw / sumTN));
423 Wq = (w > zero) ? w : zero;
424 }
425 for (std::size_t r = 0; r < K; ++r) {
426 TN(i, r) = eff[r];
427 UN(i, r) = T(TN(i, r) * map_mean(ph.PH[i][r]));
428 if (TN(i, r) > zero) {
429 RN(i, r) = T(Wq + Sact[r]);
430 QN(i, r) = T(TN(i, r) * RN(i, r));
431 } else {
432 RN(i, r) = zero;
433 QN(i, r) = zero;
434 }
435 }
436 finiteCapUsed = true;
437 } else {
438 const std::vector<T> lam = mmap_lambda(ARV[ind]);
439 T rho = zero;
440 for (std::size_t r = 0; r < K; ++r) {
441 const T u = T(lam[r] * map_mean(ph.PH[i][r]));
442 if (!std::isnan(num_traits<T>::to_double(u))) rho += u;
443 }
444 if (rho < T(one - ftol)) {
445 // A correlated single-class service is answered by the
446 // exact MAP/MAP/1 QBD, which carries the service phase
447 // across departures; MMAPPH1FCFS would discard it.
448 const bool corr =
449 (K == 1) && (ns == 1.0) && ph.known[i][0] &&
450 std::fabs(num_traits<T>::to_double(
451 map_acf(ph.PH[i][0], std::vector<unsigned>{1})[0])) >
452 GlobalConstants::CoarseTol;
453 // Correlated MAP services elsewhere: the exact MAP/MAP/c at one
454 // class, else each class's phase carried, warned at c > 1.
455 std::vector<bool> corrSvc(K, false);
456 if (!corr)
457 corrSvc = basic_detail::corr_service_classes(L, i, ph.PH[i], ph.known[i]);
458 const bool anyCorr =
459 std::find(corrSvc.begin(), corrSvc.end(), true) != corrSvc.end();
460 if (K == 1 && anyCorr && std::isfinite(ns) && ns > 1.0) {
461 try {
462 Map<T> arv;
463 arv.D0 = ARV[ind].D0;
464 arv.D1 = ARV[ind].Dc[0];
465 const Map<T> svcTrue = map_scale(
466 ph.PH[i][0],
467 T(map_mean(ph.PH[i][0]) * num_traits<T>::from_double(ns)));
468 QN(i, 0) = qsys::qsys_mapmapc(arv, svcTrue,
469 static_cast<unsigned>(std::llround(ns)),
470 static_cast<std::size_t>(500),
471 static_cast<std::size_t>(0))
472 .meanQueueLength;
473 exactStation = true;
474 } catch (const std::exception&) {
475 exactStation = false; // chain too large: carried surrogate below
476 }
477 }
478 if (anyCorr && !exactStation && !car_warned[i]) {
479 car_warned[i] = true;
480 basic_detail::warn_carried(L, i, corrSvc);
481 }
482 if (exactStation) {
483 // solved above
484 } else if (anyCorr) {
485 std::vector<Map<T>> svcK(ph.PH[i].begin(), ph.PH[i].begin() + K);
486 const qsys::MmapMap1Result<T> cr =
487 qsys::qsys_mmapmap1(ARV[ind], svcK, corrSvc);
488 for (std::size_t r = 0; r < K; ++r) QN(i, r) = cr.meanQueueLength[r];
489 } else if (corr) {
490 Map<T> arv;
491 arv.D0 = ARV[ind].D0;
492 arv.D1 = ARV[ind].Dc[0];
493 QN(i, 0) = qbd_mapmap1(arv, ph.PH[i][0]).QN;
494 } else {
495 const std::vector<T> m = mmapph1fcfs_ncmean(ARV[ind], sl);
496 for (std::size_t r = 0; r < K; ++r)
497 QN(i, r) = m[ARV[ind].classes() == 1 ? 0 : r];
498 }
499 } else {
500 // Bounded rather than left to diverge: an overloaded
501 // station holds its class's own population, or 1/FineTol
502 // when the class is open.
503 for (std::size_t r = 0; r < K; ++r)
504 QN(i, r) = std::isfinite(L.classes[r].population)
505 ? num_traits<T>::from_double(L.classes[r].population)
506 : T(one / ftol);
507 }
508 for (std::size_t r = 0; r < K; ++r) TN(i, r) = lam[r];
509 }
510 } else if (sc == SchedStrategy::PS) {
511 const std::vector<T> lam = mmap_lambda(ARV[ind]);
512 for (std::size_t r = 0; r < K; ++r) {
513 TN(i, r) = lam[r];
514 // S, NOT the server-count-scaled PH mean: the reference
515 // writes 1./sn.rates here and overwrites UN with the PH mean
516 // in the surrogate-delay block below, so this value only ever
517 // reaches the sharing denominator.
518 UN(i, r) = T(TN(i, r) * S(i, r));
519 }
520 T usum = zero;
521 for (std::size_t r = 0; r < K; ++r) usum += UN(i, r);
522 const T uden = (usum < T(one - ftol)) ? usum : T(one - ftol);
523 for (std::size_t r = 0; r < K; ++r) QN(i, r) = T(UN(i, r) / T(one - uden));
524 }
525
526 if (!finiteCapUsed) {
527 for (std::size_t r = 0; r < K; ++r) {
528 UN(i, r) = T(TN(i, r) * map_mean(ph.PH[i][r]));
529 // The jobs at the surrogate delay server the c-fold service
530 // speedup removed; an exact MAP/MAP/c station has no surrogate.
531 if (std::isfinite(ns) && !exactStation)
532 QN(i, r) = T(QN(i, r) + TN(i, r) *
533 T(map_mean(ph.PH[i][r]) *
535 num_traits<T>::from_double((ns - 1.0) / ns));
536 RN(i, r) = T(QN(i, r) / TN(i, r));
537 }
538 }
539 }
540
541 // ---- the departure processes for the next sweep -------------------
542 for (std::size_t i = 0; i < M; ++i) {
543 const std::size_t ind = L.node_of_station(i + 1) - 1;
544 const qn::NodeType ty = L.nodes[ind].nodetype;
545 const SchedStrategy sc = L.stations[i].sched;
546 if (ty == qn::NodeType::Join) {
547 for (std::size_t r = 0; r < K; ++r)
548 if (TN(i, r) > zero) DEP[ind][r] = map_exponential_mean(T(one / TN(i, r)));
549 continue;
550 }
551 if (ty != qn::NodeType::Queue || ARV[ind].order() == 0) continue;
552 const bool fcfs = (sc == SchedStrategy::FCFS || sc == SchedStrategy::HOL);
553 if (!fcfs && sc != SchedStrategy::PS) continue;
554
555 T rho = zero;
556 for (std::size_t r = 0; r < K; ++r) rho += UN(i, r);
557 for (std::size_t r = 0; r < K; ++r) {
558 const bool scalable = (V(i, r) > zero && lambda[r] > zero);
559 // The PS branch of the reference does nothing at all when the
560 // class does not flow through the station; the FCFS branch still
561 // recomputes the departure process and only skips the rescaling.
562 if (!fcfs && !scalable) continue;
563 const Mmap<T> A = mmap_hide_but(ARV[ind], r);
564 const Map<T>& Srv = ph.PH[i][r];
565 const std::size_t etaqa_sz =
566 (cfg.etaqa_trunc + 1) * A.order() * Srv.D0.rows();
567 Map<T> dep = Srv;
568 if (etaqa_sz <= tcfg.space_max && rho < T(one - ftol)) {
569 // The reference's own try/catch: an ETAQA truncation that
570 // fails to build falls back to the scaled service process.
571 try {
572 const Map<T> Am{A.D0, A.D1};
573 dep = map_normalize(fcfs ? qbd_depproc_etaqa(Am, Srv, cfg.etaqa_trunc)
574 : qbd_depproc_etaqa_ps(Am, Srv, cfg.etaqa_trunc));
575 } catch (const Error&) {
576 dep = Srv;
577 }
578 }
579 if (scalable) dep = map_scale(dep, T(one / T(lambda[r] * V(i, r))));
580 DEP[ind][r] = dep;
581 }
582 }
583
584 std::vector<T> xnew(M * K, zero);
585 for (std::size_t i = 0; i < M; ++i)
586 for (std::size_t r = 0; r < K; ++r) xnew[i * K + r] = QN(i, r);
587 return std::make_pair(xnew, xref);
588 };
589
591 fo.iter_max = static_cast<std::size_t>(opt.iter_max);
592 fo.iter_tol = opt.tol;
593 // `config.da_miniter = 3`: the legacy loop tested convergence only from the
594 // third sweep, and `config.da_norm` is the relative difference offset by
595 // FineTol so a zero reference entry does not divide by zero.
596 fo.miniter = 3;
597 fo.relative_norm = true;
598 fo.relative_eps = GlobalConstants::FineTol;
599 const da::FpiResult<T> fr = da::da_fpi<T>(sweep, std::vector<T>(M * K, zero), fo);
600 if (totiter) *totiter = fr.iterations;
601
602 // ---- the join, from the branch response times -------------------------
603 for (std::size_t j = 0; j < I; ++j) {
604 if (L.nodes[j].nodetype != qn::NodeType::Join) continue;
605 if (L.nodes[j].station == 0) continue;
606 const std::size_t jst = L.nodes[j].station - 1;
607 std::map<std::size_t, std::vector<std::size_t>> groups; // ordered, as unique() is
608 for (std::size_t b = 0; b < I; ++b) {
609 const std::size_t gid = fj.node_sync[j][b];
610 if (gid > 0) groups[gid].push_back(b);
611 }
612 for (std::size_t r = 0; r < K; ++r) {
613 if (!(TN(jst, r) > zero)) continue;
614 T syncDelay = zero, joinArrivalRate = zero;
615 for (const std::pair<const std::size_t, std::vector<std::size_t>>& g : groups) {
616 std::vector<T> branchRt, branchTput;
617 for (std::size_t b : g.second) {
618 if (L.nodes[b].station == 0) continue;
619 const std::size_t bst = L.nodes[b].station - 1;
620 if (!(RN(bst, r) > zero)) continue;
621 branchRt.push_back(RN(bst, r));
622 branchTput.push_back(TN(bst, r));
623 }
624 if (branchRt.size() < 2) continue;
625 std::vector<T> rate(branchRt.size(), zero);
626 T meanRt = zero;
627 for (std::size_t b = 0; b < branchRt.size(); ++b) {
628 rate[b] = T(one / branchRt[b]);
629 meanRt += branchRt[b];
630 }
631 meanRt /= num_traits<T>::from_int(static_cast<long>(branchRt.size()));
632 const T excess = T(exp_max_mean(rate) - meanRt);
633 if (excess > zero) syncDelay += excess;
634 for (const T& t : branchTput) joinArrivalRate += t;
635 }
636 RN(jst, r) = syncDelay;
637 QN(jst, r) = T(joinArrivalRate * syncDelay);
638 UN(jst, r) = zero;
639 }
640 }
641
643 out.Q = QN;
644 out.U = UN;
645 out.R = RN;
646 out.Tp = TN;
647 out.C.assign(K, zero);
648 for (std::size_t r = 0; r < K; ++r)
649 for (std::size_t i = 0; i < M; ++i) out.C[r] += RN(i, r);
650 // X is left at zero: the reference never assigns it. See the file header.
651 out.X.assign(K, zero);
652 zero_nans(out.Q);
653 zero_nans(out.U);
654 zero_nans(out.R);
655 zero_nans(out.Tp);
656 for (std::size_t r = 0; r < K; ++r)
657 if (std::isnan(num_traits<T>::to_double(out.C[r]))) out.C[r] = zero;
658 out.method = "dec.source.mmap";
659 out.iter = static_cast<int>(fr.iterations);
660 out.lG = 0.0;
661 return out;
662 } // if constexpr has_transcendental
663}
664
665/**
666 * Port of `solver_mam_basic_mmap_closed.m`: the per-class bisection on the
667 * surrogate arrival rate that makes the inner analyzer's queue lengths match the
668 * closed population.
669 */
670template <class T>
672 const MamOptions& opt, const MmapDecConfig& cfg,
673 std::size_t* totiter) {
674 if constexpr (!num_traits<T>::has_transcendental) {
675 (void)L; (void)opt; (void)cfg; (void)totiter;
676 throw UnsupportedError(ErrorKind{"Arithmetic"},
677 "solver_mam_basic_mmap_closed: the throughput bisection wraps the inner MMAP "
678 "decomposition, which needs transcendental arithmetic; rerun with --arith double or "
679 "--arith real");
680 } else {
681 using namespace basic_mmap_detail;
682 using basic_detail::zero_nans;
683 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
684 const std::size_t M = L.nstations, K = L.nclasses, C = L.nchains;
685
686 // REFERENCE INDEXING, CHECKED RATHER THAN ASSUMED, exactly as
687 // `solver_mna_closed` checks the same line: the terminal renormalization
688 // scales chain c's queue lengths to `sn.njobs(c)`, and `sn.njobs` is
689 // CLASS-indexed. That is only correct when each chain holds exactly one
690 // class; otherwise a chain would be scaled to another class's population,
691 // which is a wrong number with no symptom.
692 if (C != K)
693 throw UnsupportedError(ErrorKind{"MultiClass"},
694 "solver_mam_basic_mmap_closed: the reference renormalizes chain c's queue lengths with "
695 "the class-indexed sn.njobs(c), which is only correct when each chain holds exactly "
696 "one class; this model has " +
697 std::to_string(C) + " chains over " + std::to_string(K) + " classes");
698
699 // ---- the bisection bracket -------------------------------------------
700 // The upper bound is the SLOWEST rate the class meets at a finite-server
701 // station; with none, the reference falls back to the FASTEST rate over the
702 // infinite-server ones, and to 1 when the class is served nowhere.
703 std::vector<T> lambda_lb(K, zero), lambda_ub(K, zero);
704 for (std::size_t r = 0; r < K; ++r) {
705 bool any = false;
706 double best = 0.0;
707 for (std::size_t i = 0; i < M; ++i) {
708 if (!std::isfinite(L.stations[i].nservers)) continue;
709 if (L.disabled[i][r] || !(L.rates(i, r) > zero)) continue;
710 const double v = num_traits<T>::to_double(L.rates(i, r));
711 if (!any || v < best) { best = v; any = true; }
712 }
713 if (!any) {
714 for (std::size_t i = 0; i < M; ++i) {
715 if (std::isfinite(L.stations[i].nservers)) continue;
716 if (L.disabled[i][r] || !(L.rates(i, r) > zero)) continue;
717 const double v = num_traits<T>::to_double(L.rates(i, r));
718 if (!any || v > best) { best = v; any = true; }
719 }
720 }
721 lambda_ub[r] = any ? num_traits<T>::from_double(best) : one;
722 }
723 std::vector<T> lambda = lambda_ub;
724
725 std::vector<T> QNc(K, zero);
726 for (std::size_t r = 0; r < K; ++r)
727 // An open class contributes 0: only the closed populations gate the loop.
728 QNc[r] = std::isfinite(L.classes[r].population)
729 ? num_traits<T>::from_double(L.classes[r].population)
730 : zero;
731 std::vector<T> QN_chain(K, zero);
732
733 MamOptions inner = opt;
734 inner.iter_max = std::max(20, (opt.iter_max + 9) / 10);
735 // The reference caps the MMAP phase truncation at 16 here: the inner call is
736 // made once per bisection step, and the compression above that width is
737 // O(dim^3) for no accuracy the bisection can use.
738 if (inner.space_max > 16) inner.space_max = 16;
739
740 mva::MvaSolution<T> sol, last;
741 bool have_good = false, algorithm_ok = false;
742 const double bisect_tol = std::max(opt.tol, 1e-3);
743 int it_out = 0;
744
745 for (;;) {
746 double gap = 0.0;
747 for (std::size_t r = 0; r < K; ++r)
748 gap = std::max(gap, std::fabs(num_traits<T>::to_double(T(QN_chain[r] - QNc[r]))));
749 if (!(gap > bisect_tol) || it_out >= opt.iter_max) break;
750 ++it_out;
751 if (it_out > 1) {
752 bool bracket_collapsed = true;
753 for (std::size_t r = 0; r < K; ++r) {
754 if (!std::isfinite(L.classes[r].population) || !(QNc[r] > zero)) continue;
755 if (QN_chain[r] < QNc[r]) lambda_lb[r] = lambda[r];
756 else lambda_ub[r] = lambda[r];
757 lambda[r] = T(T(lambda_lb[r] + lambda_ub[r]) / num_traits<T>::from_int(2));
758 // Bisection can still refine class r only while its bracket is
759 // wider than the precision floor below which lambda cannot move
760 // any reported metric.
761 const double width =
762 num_traits<T>::to_double(T(lambda_ub[r] - lambda_lb[r]));
763 if (width > GlobalConstants::FineTol *
764 std::max(1.0, std::fabs(num_traits<T>::to_double(lambda_ub[r]))))
765 bracket_collapsed = false;
766 }
767 if (bracket_collapsed) {
768 --it_out;
769 break;
770 }
771 }
772
773 try {
774 std::size_t inner_iter = 0;
775 sol = solver_mam_basic_mmap_inner(L, inner, cfg, lambda, &inner_iter);
776 algorithm_ok = true;
777 } catch (const Error&) {
778 // The reference's own catch: the inner algorithm diverged (typically
779 // MMAPPH1FCFS under saturation). Every chain is treated as
780 // overloaded, so the next bisection step drops lambda.
781 algorithm_ok = false;
782 }
783
784 if (algorithm_ok) {
785 for (std::size_t r = 0; r < K; ++r) {
786 QN_chain[r] = zero;
787 for (std::size_t i = 0; i < M; ++i) QN_chain[r] += sol.Q(i, r);
788 if (!std::isfinite(num_traits<T>::to_double(QN_chain[r])))
789 QN_chain[r] = T(one / num_traits<T>::from_double(GlobalConstants::FineTol));
790 }
791 last = sol;
792 have_good = true;
793 } else {
794 for (std::size_t r = 0; r < K; ++r)
795 QN_chain[r] = T(one / num_traits<T>::from_double(GlobalConstants::FineTol));
796 }
797 }
798
799 // If the LAST trial diverged, fall back to the most recent successful one.
800 if (!algorithm_ok && have_good) sol = last;
801 if (sol.Q.rows() != M) {
802 // The loop never ran a trial: the initial gap was already inside the
803 // tolerance, which for a model of zero population is the honest answer.
804 sol.Q = Matrix<T>(M, K, zero);
805 sol.U = Matrix<T>(M, K, zero);
806 sol.R = Matrix<T>(M, K, zero);
807 sol.Tp = Matrix<T>(M, K, zero);
808 sol.C.assign(K, zero);
809 sol.X.assign(K, zero);
810 }
811
812 // ---- population redistribution within each chain ----------------------
813 for (std::size_t c = 0; c < C; ++c) {
814 if (!std::isfinite(L.classes[c].population)) continue;
815 T sumQ = zero;
816 for (std::size_t k : L.inchain[c])
817 for (std::size_t i = 0; i < M; ++i) sumQ += sol.Q(i, k - 1);
818 if (!(sumQ > zero)) continue;
819 const T Nc = num_traits<T>::from_double(L.classes[c].population);
820 for (std::size_t k : L.inchain[c])
821 for (std::size_t i = 0; i < M; ++i) sol.Q(i, k - 1) = T(Nc * sol.Q(i, k - 1) / sumQ);
822 }
823
824 // An infinite server's utilization IS its queue length.
825 for (std::size_t i = 0; i < M; ++i)
826 if (L.stations[i].sched == SchedStrategy::INF)
827 for (std::size_t r = 0; r < K; ++r) sol.U(i, r) = sol.Q(i, r);
828
829 sol.C.assign(K, zero);
830 for (std::size_t r = 0; r < K; ++r)
831 for (std::size_t i = 0; i < M; ++i) sol.C[r] += sol.R(i, r);
832 zero_nans(sol.Q);
833 zero_nans(sol.U);
834 zero_nans(sol.R);
835 zero_nans(sol.Tp);
836 for (std::size_t r = 0; r < K; ++r)
837 if (std::isnan(num_traits<T>::to_double(sol.C[r]))) sol.C[r] = zero;
838 sol.iter = it_out;
839 if (totiter) *totiter = static_cast<std::size_t>(it_out);
840 return sol;
841 } // if constexpr has_transcendental
842}
843
844/**
845 * Port of `solver_mam_basic_mmap.m`, the top-level dispatcher of the MMAP
846 * fork-join decomposition: an open model goes straight to the inner algorithm
847 * with the arrival rates its sources declare, a closed one through the
848 * bisection wrapper.
849 */
850template <class T>
852 const T zero = num_traits<T>::from_int(0);
853 MmapDecConfig cfg;
854 if (!L.is_open_model()) {
855 std::size_t iter = 0;
856 return solver_mam_basic_mmap_closed(L, opt, cfg, &iter);
857 }
858 // The chain's arrival rate is the total rate its classes are released at by
859 // the reference station of its first class; every class of the chain carries
860 // that same total, as the reference assigns it.
861 std::vector<T> lambda(L.nclasses, zero);
862 for (std::size_t c = 0; c < L.nchains; ++c) {
863 if (L.inchain[c].empty()) continue;
864 const std::size_t rs = L.classes[L.inchain[c][0] - 1].refstat - 1;
865 T tot = zero;
866 for (std::size_t k : L.inchain[c]) {
867 if (L.disabled[rs][k - 1]) continue;
868 const double v = num_traits<T>::to_double(L.rates(rs, k - 1));
869 if (!std::isfinite(v)) continue;
870 tot += L.rates(rs, k - 1);
871 }
872 for (std::size_t k : L.inchain[c]) lambda[k - 1] = tot;
873 }
874 std::size_t iter = 0;
875 return solver_mam_basic_mmap_inner(L, opt, cfg, lambda, &iter);
876}
877
878} // namespace mam
879} // namespace line
880
881#endif // LINE_SOLVERS_MAM_SOLVER_MAM_BASIC_MMAP_H
Base error for the multiprecision C++ port.
Definition error.h:73
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
A network plus its refreshed NetworkStruct.
std::size_t nof_nodes() const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
bool has_service_law(std::size_t i, std::size_t r) const
Does (station i, class r) have a service law an analyzer may convert?
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
std::size_t node_of_station(std::size_t st) const
1-based node index of station st, 0 when the map has no entry.
std::vector< NodeDef > nodes
every node, in creation order
bool is_open_model() const
sn_is_open_model: EVERY class is open, which is not has_open_classes.
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
The option and result types SolverMAM shares with its analyzers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Compression of a marked MAP into a smaller representation, and the two M3A primitives it is built fro...
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
The MMAP[K]/PH[K]/1 FCFS queue: per-class mean number in system and per-class queue-length distributi...
FpiResult< T > da_fpi(const std::function< std::pair< std::vector< T >, std::vector< T > >(const std::vector< T > &, std::size_t)> &iterfun, const std::vector< T > &x0, const FpiOptions &options=FpiOptions())
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
Definition da_fpi.h:102
mam::Map< T > dist_to_map(const Distrib< T > &d)
The (D0,D1) pair that reaches sn.proc.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:185
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:331
FjSyncMap sn_build_fj_sync_map(const qn::NetworkStruct< T > &sn)
sn_build_fj_sync_map.
std::vector< T > map_acf(const Map< T > &m, const std::vector< unsigned > &lags)
Autocorrelation coefficients of the inter-arrival times at the given lags,.
Definition map_moment.h:168
std::vector< T > mmapph1fcfs_ncmean(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc)
Per-class mean number of customers in the system, BUTools' 'ncMoms', 1.
MmckDetection< T > mam_detect_mmck(const qn::NetworkStruct< T > &L, std::size_t ist, const Mmap< T > &arv)
Port of mam_detect_mmck.m: is the exact M/M/c/K closed form legitimate at this station?
@ MixtureOrder1
'default', 'mixture', 'mixture.order1'
std::vector< std::vector< Map< T > > > DepTable
DEP{i,r}, the departure process of class r from i in (D0,D1) form.
mva::MvaSolution< T > solver_mam_basic_mmap_inner(const qn::NetworkStruct< T > &L, const MamOptions &opt, const MmapDecConfig &cfg, const std::vector< T > &lambda, std::size_t *totiter)
Port of solver_mam_basic_mmap_inner.m.
TrafficConfig traffic_config(const MamOptions &opt)
The traffic step's view of SolverOptions('MAM').
std::vector< T > mmap_lambda(const Mmap< T > &m)
Alias kept for parity with the MATLAB name.
Mmap< T > mmap_hide_but(const Mmap< T > &in, std::size_t keep)
mmap_hide(m, setdiff(1:K, keep)): keep ONE mark, hide every other.
mva::MvaSolution< T > solver_mam_basic_mmap(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_basic_mmap.m, the top-level dispatcher of the MMAP fork-join decomposition: an ope...
Map< T > qbd_depproc_etaqa(const Map< T > &arrival, const Map< T > &service, std::size_t n)
MAP descriptor of the departure process of a MAP/MAP/1-FCFS queue, ETAQA-truncated at level n (qbd_de...
QbdMapMap1Result< T > qbd_mapmap1(const Map< T > &arrival, const Map< T > &service_in, const T &util, std::size_t max_levels)
MAP/MAP/1 queue (qbd_mapmap1.m).
std::vector< Mmap< T > > solver_mam_traffic_mmap(const qn::NetworkStruct< T > &sn, const DepTable< T > &DEP, const TrafficConfig &config, const FjSyncMap &fjSyncMap)
Port of solver_mam_traffic_mmap.m, the fork-join aware traffic step.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
Map< T > qbd_depproc_etaqa_ps(const Map< T > &arrival, const Map< T > &service, std::size_t n)
MAP descriptor of the departure process of a MAP/MAP/1-PS queue, ETAQA-truncated at level n (qbd_depp...
Mmap< T > mmap_compress(const Mmap< T > &in, MmapCompressMethod method)
Compress an MMAP (mmap_compress.m).
Map< T > map_exponential_mean(const T &mean)
Poisson process with the given mean inter-arrival time (map_exponential.m).
Map< T > map_scale(const Map< T > &in, const T &new_mean)
Rescale time so that the mean inter-arrival time becomes new_mean.
mva::MvaSolution< T > solver_mam_basic_mmap_closed(const qn::NetworkStruct< T > &L, const MamOptions &opt, const MmapDecConfig &cfg, std::size_t *totiter)
Port of solver_mam_basic_mmap_closed.m: the per-class bisection on the surrogate arrival rate that ma...
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
Map< T > map_normalize(const Map< T > &in)
Clamp negative off-diagonal entries of D0 and negative entries of D1 to zero, then rebuild the diagon...
MapPhcResult< T > qsys_mapmapc(const mam::Map< T > &arrival, const mam::Map< T > &service, unsigned c, std::size_t dist_size=500, std::size_t num_w_moms=3)
MAP/MAP/c FCFS with the service phase carried in start order, exactly.
MmckResult< T > qsys_mmck(const T &lambda, const T &mu, unsigned c, unsigned K)
Exact analysis of the M/M/c/K queue (truncated Erlang form).
Definition qsys_mmck.h:63
MmapMap1Result< T > qsys_mmapmap1(const mam::Mmap< T > &arrival, const std::vector< mam::Map< T > > &svc, std::vector< bool > carry=std::vector< bool >())
MMAP[K]/MAP[K]/1 FCFS with per-class carried service phases, exactly.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Departure process of a MAP/MAP/1 queue: the ETAQA-truncated MAP descriptor under FCFS and under PS,...
The MAP/MAP/1 queue solved as a quasi-birth-death process.
The MAP/MAP/c FCFS queue whose service MAP is CARRIED in service-start order.
The MMAP[K]/MAP[K]/1 FCFS queue in which each class's service phase is CARRIED.
Exact analysis of the M/M/c/K queue (truncated Erlang form).
Port of solver_mam_basic.m, the dec.source analyzer and the default algorithm of SolverMAM.
The batch-arrival and batch-service queues of the MAM solver, and the two finite-capacity helpers sol...
Port of solver_mam_traffic.m and solver_mam_traffic_mmap.m: the traffic step of the dec....
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Options mirroring the fields MATLAB reads off the options struct.
Definition da_fpi.h:50
std::size_t iter_max
Definition da_fpi.h:51
std::size_t miniter
iterations before the stopping test applies
Definition da_fpi.h:54
double relative_eps
Definition da_fpi.h:72
bool relative_norm
config.da_norm, the increment norm.
Definition da_fpi.h:71
std::size_t iterations
Definition da_fpi.h:88
The MATLAB GlobalConstants, as reported by lineStart at its defaults.
Definition lang_types.h:764
sn_build_fj_sync_map: which incoming flows at a Join must be synchronized.
The options SolverMAM reads.
Definition mam_types.h:30
int iter_max
SolverOptions('MAM') lowers this from the global 1000 to 100.
Definition mam_types.h:34
std::size_t space_max
options.config.space_max: the order budget of the per-station arrival superposition,...
Definition mam_types.h:40
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
The options.config fields the MMAP decomposition reads on top of MamOptions.
std::size_t etaqa_trunc
config.etaqa_trunc, the ETAQA level truncation of the departure process.
std::size_t fj_sync_q_len
config.fj_sync_q_len, the synchronization queue at a join.
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
Matrix< T > D0
Definition mmap_lambda.h:46
Matrix< T > D1
Definition mmap_lambda.h:47
std::size_t order() const
Definition mmap_lambda.h:50
What mam_detect_mmck returns; muRate is meaningful only when isMmck.
The fields of options.config the traffic step reads.
std::size_t fj_sync_q_len
config.fj_sync_q_len, the join's synchronization queue; MATLAB's default.
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:97
std::vector< T > X
Definition mva_types.h:99
double lG
log of the normalizing constant, the reference's lG.
Definition mva_types.h:119
std::vector< T > C
Definition mva_types.h:99
Return value of qsys_mmapmap1, mirroring the MATLAB struct.
std::vector< T > meanQueueLength
E[N_k].
T meanQueueLength
L, mean number in system.
Definition qsys_mmck.h:44