LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_predicates.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_API_SN_SN_PREDICATES_H
6#define LINE_API_SN_SN_PREDICATES_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Ports of the sn_has_* / sn_is_* predicate family of matlab/src/api/sn.
12 *
13 * The reference keeps each predicate in its own one-line .m file; collecting
14 * them in one header keeps the port readable and, more importantly, keeps the
15 * DEFINITIONS in one place. Several of these predicates gate solver dispatch,
16 * so a predicate that quietly differs from the reference does not raise an
17 * error -- it routes the model to a different algorithm and returns a
18 * different number.
19 *
20 * NetworkStruct carries member functions with some of these names. Where both
21 * exist the free function here is the reference-faithful one and the member is
22 * a convenience used inside the struct's own refresh; sn_has_product_form is
23 * the case where the two DISAGREE, see the note on that function.
24 * sn_has_blocking and sn_is_mm1k_loss are the other way round: the SOLVERS read
25 * them through NetworkStruct::has_product_form, so the body lives on the struct
26 * and these forward to it rather than carrying a second copy.
27 *
28 * ARITHMETIC: field. Every test is a comparison; nothing here is
29 * transcendental, so the whole family instantiates under Rational.
30 */
31
32#include <cmath>
33#include <cstddef>
34#include <limits>
35#include <vector>
36
40#include "line/num/number.h"
41
42namespace line {
43namespace api {
44
45namespace detail {
46
47/** True when some station runs `s`, the shape shared by the sn_has_<sched> family. */
48template <class T>
49bool sn_any_sched(const qn::NetworkStruct<T>& sn, qn::SchedStrategy s) {
50 for (std::size_t i = 0; i < sn.stations.size(); ++i)
51 if (sn.stations[i].sched == s) return true;
52 return false;
53}
54
55} // namespace detail
56
57// ---- the sn_has_<discipline> family ------------------------------------
58
59template <class T>
61 return detail::sn_any_sched(sn, qn::SchedStrategy::DPS);
62}
63template <class T>
65 return detail::sn_any_sched(sn, qn::SchedStrategy::DPSPRIO);
66}
67template <class T>
69 return detail::sn_any_sched(sn, qn::SchedStrategy::FCFS);
70}
71template <class T>
73 return detail::sn_any_sched(sn, qn::SchedStrategy::GPS);
74}
75template <class T>
77 return detail::sn_any_sched(sn, qn::SchedStrategy::GPSPRIO);
78}
79template <class T>
81 return detail::sn_any_sched(sn, qn::SchedStrategy::HOL);
82}
83template <class T>
85 return detail::sn_any_sched(sn, qn::SchedStrategy::INF);
86}
87template <class T>
89 return detail::sn_any_sched(sn, qn::SchedStrategy::LCFS);
90}
91template <class T>
93 return detail::sn_any_sched(sn, qn::SchedStrategy::LCFSPI);
94}
95template <class T>
97 return detail::sn_any_sched(sn, qn::SchedStrategy::LCFSPR);
98}
99template <class T>
101 return detail::sn_any_sched(sn, qn::SchedStrategy::LEPT);
102}
103template <class T>
105 return detail::sn_any_sched(sn, qn::SchedStrategy::LJF);
106}
107template <class T>
109 return detail::sn_any_sched(sn, qn::SchedStrategy::LPS);
110}
111template <class T>
113 return detail::sn_any_sched(sn, qn::SchedStrategy::POLLING);
114}
115template <class T>
117 return detail::sn_any_sched(sn, qn::SchedStrategy::PS);
118}
119template <class T>
121 return detail::sn_any_sched(sn, qn::SchedStrategy::PSPRIO);
122}
123template <class T>
125 return detail::sn_any_sched(sn, qn::SchedStrategy::SEPT);
126}
127template <class T>
129 return detail::sn_any_sched(sn, qn::SchedStrategy::SETF);
130}
131template <class T>
133 return detail::sn_any_sched(sn, qn::SchedStrategy::SIRO);
134}
135template <class T>
137 return detail::sn_any_sched(sn, qn::SchedStrategy::SJF);
138}
139template <class T>
141 return detail::sn_any_sched(sn, qn::SchedStrategy::SRPT);
142}
143
144/**
145 * Port of sn_has_homogeneous_scheduling.
146 *
147 * The reference is `length(findstring(sn.sched, strategy)) == sn.nstations`,
148 * and findstring compares STRINGS: against the numeric sched vector its strcmp
149 * is false everywhere, so it returns the sentinel -1, whose length is 1. The
150 * predicate therefore reduces to nstations == 1 whatever the disciplines are.
151 * Reproduced and not corrected: this is the branch every codebase takes.
152 */
153template <class T>
155 return sn.nstations == 1;
156}
157
158// ---- class and chain counts -------------------------------------------
159
160template <class T>
162 for (std::size_t k = 0; k < sn.classes.size(); ++k)
163 if (std::isinf(sn.classes[k].population)) return true;
164 return false;
165}
166
167template <class T>
169 for (std::size_t k = 0; k < sn.classes.size(); ++k)
170 if (std::isfinite(sn.classes[k].population)) return true;
171 return false;
172}
173
174template <class T>
176 std::size_t n = 0;
177 for (std::size_t k = 0; k < sn.classes.size(); ++k)
178 if (std::isfinite(sn.classes[k].population)) ++n;
179 return n > 1;
180}
181
182template <class T>
186
187template <class T>
189 return sn.nchains > 1;
190}
191template <class T>
193 return sn.nchains == 1;
194}
195template <class T>
197 return sn.nclasses > 1;
198}
199template <class T>
201 return sn.nclasses == 1;
202}
203template <class T>
205 return sn.nclasses != sn.nchains;
206}
207
208/** `all(isinf(sn.njobs))`: EVERY class is open, which a mixed model fails. */
209template <class T>
211 for (std::size_t k = 0; k < sn.classes.size(); ++k)
212 if (!std::isinf(sn.classes[k].population)) return false;
213 return true;
214}
215
216/** `all(isfinite(sn.njobs))`: EVERY class is closed. */
217template <class T>
219 for (std::size_t k = 0; k < sn.classes.size(); ++k)
220 if (!std::isfinite(sn.classes[k].population)) return false;
221 return true;
222}
223
224template <class T>
228
229// ---- structural predicates --------------------------------------------
230
231template <class T>
233 for (std::size_t k = 0; k < sn.classes.size(); ++k)
234 if (sn.classes[k].prio > 0) return true;
235 return false;
236}
237
238template <class T>
240 for (std::size_t i = 0; i < sn.stations.size(); ++i)
241 if (std::isfinite(sn.stations[i].nservers) && sn.stations[i].nservers > 1.0) return true;
242 return false;
243}
244
245/** `any(sn.njobs ~= round(sn.njobs))`; an infinite population rounds to itself. */
246template <class T>
248 for (std::size_t k = 0; k < sn.classes.size(); ++k) {
249 const double n = sn.classes[k].population;
250 if (std::isfinite(n) && n != std::floor(n + 0.5)) return true;
251 }
252 return false;
253}
254
255/** `size(sn.lldscaling,2)>0`: some station carries a limited-load scaling row. */
256template <class T>
258 for (std::size_t i = 0; i < sn.stations.size(); ++i)
259 if (!sn.stations[i].lldscaling.empty()) return true;
260 return false;
261}
262
263/**
264 * `any(sn.fj(:) > 0)`: the model has a Fork closed by a Join.
265 *
266 * `sn.fj` is the (fork, join) pair list here, so non-empty is the predicate.
267 * It is populated on the UN-augmented struct too, exactly as MATLAB's fj
268 * matrix is, so this agrees with the reference before and after fj_tag.
269 */
270template <class T>
272 return !sn.fj.empty();
273}
274
275/**
276 * `sn_has_sd_routing`: some node dispatches on the STATE of the network.
277 *
278 * RROBIN, WRROBIN, JSQ and SQ. RROBIN matters even though the refresh spreads
279 * its probabilities uniformly: the uniform matrix is the right MEAN and the
280 * wrong higher moments, so a round-robin model is not product form and must
281 * not be handed to exact MVA or to the convolution algorithm.
282 */
283template <class T>
285 for (std::size_t a = 0; a < sn.nodes.size(); ++a)
286 for (std::size_t r = 0; r < sn.nodes[a].routing.size(); ++r) {
287 const qn::RoutingStrategy s = sn.nodes[a].routing[r];
288 if (s == qn::RoutingStrategy::RROBIN || s == qn::RoutingStrategy::WRROBIN ||
289 s == qn::RoutingStrategy::JSQ || s == qn::RoutingStrategy::SQ ||
290 s == qn::RoutingStrategy::SDR)
291 return true;
292 }
293 return false;
294}
295
296/** `sum(sn.rates(i,:)>0)>1` at some FCFS station: more than one class is served there. */
297template <class T>
299 const T zero = num_traits<T>::from_int(0);
300 for (std::size_t i = 0; i < sn.nstations; ++i) {
301 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
302 std::size_t n = 0;
303 for (std::size_t r = 0; r < sn.nclasses; ++r) {
304 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
305 if (sn.rates(i, r) > zero) ++n;
306 }
307 if (n > 1) return true;
308 }
309 return false;
310}
311
312/**
313 * `sn_has_multi_class_heter_fcfs`: an FCFS station whose per-class rates differ.
314 *
315 * MATLAB filters the row with `isfinite(rates) & ~isnan(rates)`, which drops
316 * BOTH the NaN of a disabled pair and the Inf of an Immediate service. The
317 * disabled flag covers the first; the isfinite test below covers the second,
318 * without which an Immediate-served class would make every FCFS station look
319 * heterogeneous and take product form away from the model.
320 */
321template <class T>
323 for (std::size_t i = 0; i < sn.nstations; ++i) {
324 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
325 bool any = false;
326 double lo = 0.0, hi = 0.0;
327 for (std::size_t r = 0; r < sn.nclasses; ++r) {
328 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
329 const double v = num_traits<T>::to_double(sn.rates(i, r));
330 if (!std::isfinite(v)) continue;
331 if (!any) {
332 lo = hi = v;
333 any = true;
334 } else {
335 if (v < lo) lo = v;
336 if (v > hi) hi = v;
337 }
338 }
339 // Relative FineTol, not > 0: a one-ulp spread in LN layer rates flipped exact MVA vs AMVA
340 if (any && hi - lo > qn::GlobalConstants::FineTol * std::fabs(lo)) return true;
341 }
342 return false;
343}
344
345/** An FCFS station with heterogeneous rates whose SCVs are all one (exponential). */
346template <class T>
348 for (std::size_t i = 0; i < sn.nstations; ++i) {
349 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
350 // MATLAB tests `range(sn.rates(i,:)) > 0`, and range's max and min skip
351 // NaN but keep Inf -- unlike sn_has_multi_class_heter_fcfs, which
352 // filters on isfinite. The two predicates really do differ there.
353 bool any = false;
354 double lo = 0.0, hi = 0.0;
355 for (std::size_t r = 0; r < sn.nclasses; ++r) {
356 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
357 const double v = num_traits<T>::to_double(sn.rates(i, r));
358 if (std::isnan(v)) continue;
359 if (!any) {
360 lo = hi = v;
361 any = true;
362 } else {
363 if (v < lo) lo = v;
364 if (v > hi) hi = v;
365 }
366 }
367 if (!(any && hi - lo > 0.0)) continue;
368 // max/min over the scv row also skip NaN, so a disabled pair neither
369 // qualifies nor disqualifies the station; an all-disabled row leaves
370 // max([]) empty and the reference's `if` false, hence `seen`.
371 bool allone = true, seen = false;
372 for (std::size_t r = 0; r < sn.nclasses && allone; ++r) {
373 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
374 const double c = num_traits<T>::to_double(sn.scv(i, r));
375 if (std::isnan(c)) continue;
376 seen = true;
377 if (!(c < 1.0 + qn::GlobalConstants::FineTol && c > 1.0 - qn::GlobalConstants::FineTol))
378 allone = false;
379 }
380 if (seen && allone) return true;
381 }
382 return false;
383}
384
385/** The disciplines the reference admits in sn_has_product_form. */
386template <class T>
388 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
389 const qn::SchedStrategy s = sn.stations[i].sched;
390 if (!(s == qn::SchedStrategy::INF || s == qn::SchedStrategy::PS ||
391 s == qn::SchedStrategy::FCFS || s == qn::SchedStrategy::LCFSPR ||
392 s == qn::SchedStrategy::LCFS || s == qn::SchedStrategy::EXT))
393 return false;
394 }
395 return true;
396}
397
398/** Defined below, after sn_is_mm1k_loss, which it exempts. */
399template <class T>
401
402/**
403 * Port of sn_has_product_form.
404 *
405 * NOTE. `NetworkStruct::has_product_form` omits the fork-join and the
406 * state-dependent-routing conjuncts the reference carries, so it answers TRUE
407 * on a model with a surviving Fork or a round-robin dispatcher. That is not a
408 * cosmetic difference: the predicate gates exact MVA, the NC convolution and
409 * the AUTO solver tree, so the omission silently hands a non-product-form
410 * model to an exact algorithm. This free function is the reference-faithful
411 * one and callers in the api layer must use it.
412 */
413template <class T>
415 // BCMP asks for infinite buffers. Nothing here read sn.cap/sn.classcap/
416 // sn.droprule, so a BAS-blocked station or any binding finite buffer passed the
417 // gate and the network read as product form while its truncation couples the
418 // station occupancies.
421 !sn_has_blocking(sn) && sn.has_exponential_fcfs();
422}
423
424/**
425 * Port of sn_has_product_form_not_het_fcfs: LCFS is out, and every enabled
426 * FCFS pair with a finite positive SCV must have that SCV equal to one, with
427 * the service means agreeing across the classes served there.
428 *
429 * CHECK_MEANS drops that second half; pass false only for an algorithm that
430 * models class-dependent FCFS itself (ab, schmidt, schmidt-ext).
431 */
432template <class T>
433bool sn_has_product_form_not_het_fcfs(const qn::NetworkStruct<T>& sn, bool check_means = true) {
434 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
435 const qn::SchedStrategy s = sn.stations[i].sched;
436 if (!(s == qn::SchedStrategy::INF || s == qn::SchedStrategy::PS ||
437 s == qn::SchedStrategy::FCFS || s == qn::SchedStrategy::LCFSPR ||
438 s == qn::SchedStrategy::EXT))
439 return false;
440 }
441 if (sn_has_priorities(sn) || sn_has_fork_join(sn) || sn_has_sd_routing(sn)) return false;
442 for (std::size_t i = 0; i < sn.nstations; ++i) {
443 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
444 // BCMP type 1 asks the FCFS service to be exponential AND
445 // class-independent, so the means are checked alongside the SCVs: with
446 // unequal means the product-form solve returns a wait proportional to
447 // each class's own demand where FCFS makes every class wait behind the
448 // same queue. The mean comparison is between CHAIN service times
449 // (visit-weighted over the classes that actually visit the station): a
450 // class that never visits cannot break product form, and within-chain
451 // heterogeneity is invisible to both the product-form and the qd
452 // branch, which deaggregate a chain result proportionally to each
453 // class's own demand, so only between-chain heterogeneity warrants the
454 // divert. LN layers carry seeded rates for classes with zero visits,
455 // which a raw per-class comparison mistakes for heterogeneity.
456 for (std::size_t r = 0; r < sn.nclasses; ++r) {
457 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
458 const double v = num_traits<T>::to_double(sn.scv(i, r));
459 if (std::isfinite(v) && v > 0.0 &&
461 return false;
462 }
463 if (!check_means || sn.visits.empty()) continue;
464 const std::size_t isf = sn.stateful_of_station(i + 1) - 1;
465 double stmin = 0.0, stmax = 0.0;
466 bool anyserved = false;
467 for (std::size_t c = 0; c < sn.nchains && c < sn.visits.size(); ++c) {
468 double num = 0.0, den = 0.0;
469 for (std::size_t r = 0; r < sn.nclasses; ++r) {
470 if (!sn.chains.empty() && !sn.chains[c][r]) continue;
471 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
472 const double w = num_traits<T>::to_double(sn.visits[c](isf, r));
473 const double rate = num_traits<T>::to_double(sn.rates(i, r));
474 if (w > qn::GlobalConstants::Zero && std::isfinite(rate) && rate > 0.0) {
475 num += w / rate;
476 den += w;
477 }
478 }
479 if (den > 0.0) {
480 const double st = num / den;
481 if (!anyserved) {
482 stmin = stmax = st;
483 anyserved = true;
484 } else {
485 stmin = std::min(stmin, st);
486 stmax = std::max(stmax, st);
487 }
488 }
489 }
490 if (anyserved && stmax - stmin > qn::GlobalConstants::CoarseTol * stmax) return false;
491 }
492 return true;
493}
494
495/**
496 * `sn_is_population_model`: every station is population dependent only, which
497 * is what lets the fluid and mean-field limits close on the queue lengths.
498 */
499template <class T>
501 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
502 const qn::SchedStrategy s = sn.stations[i].sched;
503 if (!(s == qn::SchedStrategy::INF || s == qn::SchedStrategy::PS ||
504 s == qn::SchedStrategy::PSPRIO || s == qn::SchedStrategy::DPS ||
505 s == qn::SchedStrategy::GPS || s == qn::SchedStrategy::GPSPRIO ||
506 s == qn::SchedStrategy::DPSPRIO || s == qn::SchedStrategy::EXT))
507 return false;
508 }
510}
511
512/**
513 * `sn_is_bas_model`: a single-class closed model with blocking-after-service.
514 */
515template <class T>
517 if (sn.nclasses != 1) return false;
518 double closed = 0.0;
519 for (std::size_t k = 0; k < sn.classes.size(); ++k) {
520 if (std::isinf(sn.classes[k].population)) return false; // open class present
521 closed += sn.classes[k].population;
522 }
523 if (!(closed > 0.0)) return false;
524 // `sn.droprule`, the REFRESHED (nstations x nclasses) table, not the
525 // per-station user declaration: refresh_bas_blocking propagates a
526 // destination-declared BAS rule into the table, and reading the
527 // declaration would miss exactly those models
528 for (std::size_t i = 0; i < sn.droprule.size(); ++i)
529 for (std::size_t r = 0; r < sn.droprule[i].size(); ++r)
530 if (sn.droprule[i][r] == qn::DropStrategy::BAS) return true;
531 return false;
532}
533
534/**
535 * `sn_is_mm1k_loss`: the three-node Source/Queue/Sink model of an M/M/1/K with
536 * loss, the shape MVA answers in closed form instead of iterating.
537 */
538template <class T>
540 return sn.is_mm1k_loss();
541}
542
543/**
544 * Port of sn_has_blocking: some station can REFUSE a job.
545 *
546 * Either its own buffer binds -- Kendall's K below the population that can
547 * reach it, whatever the drop rule (WAITQ, DROP, BAS, BBS, RSRD) -- or a finite
548 * capacity region caps a set of stations jointly. Such a network is not product
549 * form: the truncation couples the station occupancies, so no BCMP
550 * factorization of the equilibrium distribution exists.
551 *
552 * Only a buffer that can actually BIND counts, which is what
553 * sn_get_buffer_size decides; refresh_capacity derives a finite classcap (the
554 * chain population) at every station of every closed model, so a plain
555 * finiteness test would call every closed model blocking.
556 *
557 * A Cache is exempt: it builds its own capped retrieval queues (classcap 1),
558 * which the cache analyzers solve rather than treat as a buffer constraint. So
559 * is the single-station M/M/1/K loss system, whose truncated geometric
560 * distribution is a product form over its one station.
561 */
562template <class T>
564 return sn.has_blocking();
565}
566
567} // namespace api
568} // namespace line
569
570#endif // LINE_API_SN_SN_PREDICATES_H
A network plus its refreshed NetworkStruct.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
bool sn_has_siro(const qn::NetworkStruct< T > &sn)
bool sn_has_multi_class_heter_fcfs(const qn::NetworkStruct< T > &sn)
sn_has_multi_class_heter_fcfs: an FCFS station whose per-class rates differ.
bool sn_has_hol(const qn::NetworkStruct< T > &sn)
bool sn_has_mixed_classes(const qn::NetworkStruct< T > &sn)
bool sn_has_ljf(const qn::NetworkStruct< T > &sn)
bool sn_has_multi_chain(const qn::NetworkStruct< T > &sn)
bool sn_has_ps(const qn::NetworkStruct< T > &sn)
bool sn_has_multi_class_fcfs(const qn::NetworkStruct< T > &sn)
sum(sn.rates(i,:)>0)>1 at some FCFS station: more than one class is served there.
bool sn_is_open_model(const qn::NetworkStruct< T > &sn)
all(isinf(sn.njobs)): EVERY class is open, which a mixed model fails.
bool sn_has_lcfs_pi(const qn::NetworkStruct< T > &sn)
bool sn_has_closed_classes(const qn::NetworkStruct< T > &sn)
bool sn_has_product_form(const qn::NetworkStruct< T > &sn)
Port of sn_has_product_form.
bool sn_has_ps_prio(const qn::NetworkStruct< T > &sn)
bool sn_has_sept(const qn::NetworkStruct< T > &sn)
bool sn_has_homogeneous_scheduling(const qn::NetworkStruct< T > &sn, qn::SchedStrategy)
Port of sn_has_homogeneous_scheduling.
bool sn_has_dps(const qn::NetworkStruct< T > &sn)
bool sn_has_load_dependence(const qn::NetworkStruct< T > &sn)
size(sn.lldscaling,2)>0: some station carries a limited-load scaling row.
bool sn_has_single_class(const qn::NetworkStruct< T > &sn)
bool sn_has_multiple_closed_classes(const qn::NetworkStruct< T > &sn)
bool sn_has_srpt(const qn::NetworkStruct< T > &sn)
bool sn_has_lps(const qn::NetworkStruct< T > &sn)
bool sn_has_sd_routing(const qn::NetworkStruct< T > &sn)
sn_has_sd_routing: some node dispatches on the STATE of the network.
bool sn_is_population_model(const qn::NetworkStruct< T > &sn)
sn_is_population_model: every station is population dependent only, which is what lets the fluid and ...
bool sn_has_class_switching(const qn::NetworkStruct< T > &sn)
bool sn_is_closed_model(const qn::NetworkStruct< T > &sn)
all(isfinite(sn.njobs)): EVERY class is closed.
bool sn_is_mixed_model(const qn::NetworkStruct< T > &sn)
bool sn_has_product_form_not_het_fcfs(const qn::NetworkStruct< T > &sn, bool check_means=true)
Port of sn_has_product_form_not_het_fcfs: LCFS is out, and every enabled FCFS pair with a finite posi...
bool sn_has_sjf(const qn::NetworkStruct< T > &sn)
bool sn_is_bas_model(const qn::NetworkStruct< T > &sn)
sn_is_bas_model: a single-class closed model with blocking-after-service.
bool sn_has_lcfs_pr(const qn::NetworkStruct< T > &sn)
bool sn_has_fractional_populations(const qn::NetworkStruct< T > &sn)
any(sn.njobs ~= round(sn.njobs)); an infinite population rounds to itself.
bool sn_has_lcfs(const qn::NetworkStruct< T > &sn)
bool sn_has_multi_server(const qn::NetworkStruct< T > &sn)
bool sn_has_multi_class(const qn::NetworkStruct< T > &sn)
bool sn_has_priorities(const qn::NetworkStruct< T > &sn)
bool sn_has_dps_prio(const qn::NetworkStruct< T > &sn)
bool sn_has_multi_class_heter_exp_fcfs(const qn::NetworkStruct< T > &sn)
An FCFS station with heterogeneous rates whose SCVs are all one (exponential).
bool sn_has_fork_join(const qn::NetworkStruct< T > &sn)
any(sn.fj(:) > 0): the model has a Fork closed by a Join.
bool sn_has_blocking(const qn::NetworkStruct< T > &sn)
Defined below, after sn_is_mm1k_loss, which it exempts.
bool sn_has_setf(const qn::NetworkStruct< T > &sn)
bool sn_has_lept(const qn::NetworkStruct< T > &sn)
bool sn_has_gps_prio(const qn::NetworkStruct< T > &sn)
bool sn_has_inf(const qn::NetworkStruct< T > &sn)
bool sn_has_polling(const qn::NetworkStruct< T > &sn)
bool sn_has_single_chain(const qn::NetworkStruct< T > &sn)
bool sn_has_open_classes(const qn::NetworkStruct< T > &sn)
bool sn_has_fcfs(const qn::NetworkStruct< T > &sn)
bool sn_has_gps(const qn::NetworkStruct< T > &sn)
bool sn_sched_is_product_form(const qn::NetworkStruct< T > &sn)
The disciplines the reference admits in sn_has_product_form.
bool sn_is_mm1k_loss(const qn::NetworkStruct< T > &sn)
sn_is_mm1k_loss: the three-node Source/Queue/Sink model of an M/M/1/K with loss, the shape MVA answer...
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
Definition lang_types.h:391
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Physical buffer size of a station, in jobs, the one in service included.
static constexpr double FineTol
Definition lang_types.h:760
static constexpr double Zero
Definition lang_types.h:762
static constexpr double CoarseTol
Definition lang_types.h:761