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 <map>
36#include <vector>
37
41#include "line/num/number.h"
42
43namespace line {
44namespace api {
45
46namespace detail {
47
48/** True when some station runs `s`, the shape shared by the sn_has_<sched> family. */
49template <class T>
50bool sn_any_sched(const qn::NetworkStruct<T>& sn, qn::SchedStrategy s) {
51 for (std::size_t i = 0; i < sn.stations.size(); ++i)
52 if (sn.stations[i].sched == s) return true;
53 return false;
54}
55
56} // namespace detail
57
58// ---- the sn_has_<discipline> family ------------------------------------
59
60template <class T>
62 return detail::sn_any_sched(sn, qn::SchedStrategy::DPS);
63}
64template <class T>
66 return detail::sn_any_sched(sn, qn::SchedStrategy::DPSPRIO);
67}
68template <class T>
70 return detail::sn_any_sched(sn, qn::SchedStrategy::FCFS);
71}
72template <class T>
74 return detail::sn_any_sched(sn, qn::SchedStrategy::GPS);
75}
76template <class T>
78 return detail::sn_any_sched(sn, qn::SchedStrategy::GPSPRIO);
79}
80template <class T>
82 return detail::sn_any_sched(sn, qn::SchedStrategy::HOL);
83}
84template <class T>
86 return detail::sn_any_sched(sn, qn::SchedStrategy::INF);
87}
88template <class T>
90 return detail::sn_any_sched(sn, qn::SchedStrategy::LCFS);
91}
92template <class T>
94 return detail::sn_any_sched(sn, qn::SchedStrategy::LCFSPI);
95}
96template <class T>
98 return detail::sn_any_sched(sn, qn::SchedStrategy::LCFSPR);
99}
100template <class T>
102 return detail::sn_any_sched(sn, qn::SchedStrategy::LEPT);
103}
104template <class T>
106 return detail::sn_any_sched(sn, qn::SchedStrategy::LJF);
107}
108template <class T>
110 return detail::sn_any_sched(sn, qn::SchedStrategy::LPS);
111}
112template <class T>
114 return detail::sn_any_sched(sn, qn::SchedStrategy::POLLING);
115}
116template <class T>
118 return detail::sn_any_sched(sn, qn::SchedStrategy::PS);
119}
120template <class T>
122 return detail::sn_any_sched(sn, qn::SchedStrategy::PSPRIO);
123}
124template <class T>
126 return detail::sn_any_sched(sn, qn::SchedStrategy::SEPT);
127}
128template <class T>
130 return detail::sn_any_sched(sn, qn::SchedStrategy::SETF);
131}
132template <class T>
134 return detail::sn_any_sched(sn, qn::SchedStrategy::SIRO);
135}
136template <class T>
138 return detail::sn_any_sched(sn, qn::SchedStrategy::SJF);
139}
140template <class T>
142 return detail::sn_any_sched(sn, qn::SchedStrategy::SRPT);
143}
144
145/**
146 * Port of sn_has_homogeneous_scheduling: true when EVERY station uses
147 * `strategy`, the reference's `length(findstring(sn.sched, strategy)) ==
148 * sn.nstations` (the count of stations whose discipline is `strategy` equals
149 * the station count), as the JAR and Python ports evaluate it. This port used
150 * to reduce it to `nstations == 1`; it now follows the reference.
151 */
152template <class T>
154 std::size_t count = 0;
155 for (std::size_t i = 0; i < sn.nstations && i < sn.stations.size(); ++i)
156 if (sn.stations[i].sched == strategy) ++count;
157 return count == sn.nstations;
158}
159
160/**
161 * Port of sn_has_quorum_join: true when some Join node declares a non-STD
162 * strategy with a positive required count. The sibling count is NOT
163 * re-derived here, so a declaration with k >= n still reads as a quorum, as
164 * in the reference; `NetworkStruct::quorum_joins()` is the stricter k < n test.
165 */
166template <class T>
168 for (typename std::map<std::size_t, typename qn::NetworkStruct<T>::JoinDecl>::const_iterator
169 it = sn.joindecl.begin();
170 it != sn.joindecl.end(); ++it) {
171 if (it->second.strategy == lang::JoinStrategy::STD) continue;
172 if (it->second.quorum > 0.0) return true;
173 }
174 return false;
175}
176
177// ---- class and chain counts -------------------------------------------
178
179template <class T>
181 for (std::size_t k = 0; k < sn.classes.size(); ++k)
182 if (std::isinf(sn.classes[k].population)) return true;
183 return false;
184}
185
186template <class T>
188 for (std::size_t k = 0; k < sn.classes.size(); ++k)
189 if (std::isfinite(sn.classes[k].population)) return true;
190 return false;
191}
192
193template <class T>
195 std::size_t n = 0;
196 for (std::size_t k = 0; k < sn.classes.size(); ++k)
197 if (std::isfinite(sn.classes[k].population)) ++n;
198 return n > 1;
199}
200
201template <class T>
205
206template <class T>
208 return sn.nchains > 1;
209}
210template <class T>
212 return sn.nchains == 1;
213}
214template <class T>
216 return sn.nclasses > 1;
217}
218template <class T>
220 return sn.nclasses == 1;
221}
222template <class T>
224 return sn.nclasses != sn.nchains;
225}
226
227/** `all(isinf(sn.njobs))`: EVERY class is open, which a mixed model fails. */
228template <class T>
230 for (std::size_t k = 0; k < sn.classes.size(); ++k)
231 if (!std::isinf(sn.classes[k].population)) return false;
232 return true;
233}
234
235/** `all(isfinite(sn.njobs))`: EVERY class is closed. */
236template <class T>
238 for (std::size_t k = 0; k < sn.classes.size(); ++k)
239 if (!std::isfinite(sn.classes[k].population)) return false;
240 return true;
241}
242
243template <class T>
247
248// ---- structural predicates --------------------------------------------
249
250template <class T>
252 for (std::size_t k = 0; k < sn.classes.size(); ++k)
253 if (sn.classes[k].prio > 0) return true;
254 return false;
255}
256
257template <class T>
259 for (std::size_t i = 0; i < sn.stations.size(); ++i)
260 if (std::isfinite(sn.stations[i].nservers) && sn.stations[i].nservers > 1.0) return true;
261 return false;
262}
263
264/** `any(sn.njobs ~= round(sn.njobs))`; an infinite population rounds to itself. */
265template <class T>
267 for (std::size_t k = 0; k < sn.classes.size(); ++k) {
268 const double n = sn.classes[k].population;
269 if (std::isfinite(n) && n != std::floor(n + 0.5)) return true;
270 }
271 return false;
272}
273
274/** `size(sn.lldscaling,2)>0`: some station carries a limited-load scaling row. */
275template <class T>
277 for (std::size_t i = 0; i < sn.stations.size(); ++i)
278 if (!sn.stations[i].lldscaling.empty()) return true;
279 return false;
280}
281
282/**
283 * `any(sn.fj(:) > 0)`: the model has a Fork closed by a Join.
284 *
285 * `sn.fj` is the (fork, join) pair list here, so non-empty is the predicate.
286 * It is populated on the UN-augmented struct too, exactly as MATLAB's fj
287 * matrix is, so this agrees with the reference before and after fj_tag.
288 */
289template <class T>
291 return !sn.fj.empty();
292}
293
294/**
295 * `sn_has_sd_routing`: some node dispatches on the STATE of the network.
296 *
297 * RROBIN, WRROBIN, JSQ and SQ. RROBIN matters even though the refresh spreads
298 * its probabilities uniformly: the uniform matrix is the right MEAN and the
299 * wrong higher moments, so a round-robin model is not product form and must
300 * not be handed to exact MVA or to the convolution algorithm.
301 */
302template <class T>
304 for (std::size_t a = 0; a < sn.nodes.size(); ++a)
305 for (std::size_t r = 0; r < sn.nodes[a].routing.size(); ++r) {
306 const qn::RoutingStrategy s = sn.nodes[a].routing[r];
307 if (s == qn::RoutingStrategy::RROBIN || s == qn::RoutingStrategy::WRROBIN ||
308 s == qn::RoutingStrategy::JSQ || s == qn::RoutingStrategy::SQ ||
309 s == qn::RoutingStrategy::SDR)
310 return true;
311 }
312 return false;
313}
314
315/** `sum(sn.rates(i,:)>0)>1` at some FCFS station: more than one class is served there. */
316template <class T>
318 const T zero = num_traits<T>::from_int(0);
319 for (std::size_t i = 0; i < sn.nstations; ++i) {
320 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
321 std::size_t n = 0;
322 for (std::size_t r = 0; r < sn.nclasses; ++r) {
323 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
324 if (sn.rates(i, r) > zero) ++n;
325 }
326 if (n > 1) return true;
327 }
328 return false;
329}
330
331/**
332 * `sn_has_multi_class_heter_fcfs`: an FCFS station whose per-class rates differ.
333 *
334 * MATLAB filters the row with `isfinite(rates) & ~isnan(rates)`, which drops
335 * BOTH the NaN of a disabled pair and the Inf of an Immediate service. The
336 * disabled flag covers the first; the isfinite test below covers the second,
337 * without which an Immediate-served class would make every FCFS station look
338 * heterogeneous and take product form away from the model.
339 */
340template <class T>
342 for (std::size_t i = 0; i < sn.nstations; ++i) {
343 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
344 bool any = false;
345 double lo = 0.0, hi = 0.0;
346 for (std::size_t r = 0; r < sn.nclasses; ++r) {
347 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
348 const double v = num_traits<T>::to_double(sn.rates(i, r));
349 if (!std::isfinite(v)) continue;
350 if (!any) {
351 lo = hi = v;
352 any = true;
353 } else {
354 if (v < lo) lo = v;
355 if (v > hi) hi = v;
356 }
357 }
358 // Relative FineTol, not > 0: a one-ulp spread in LN layer rates flipped exact MVA vs AMVA
359 if (any && hi - lo > qn::GlobalConstants::FineTol * std::fabs(lo)) return true;
360 }
361 return false;
362}
363
364/** An FCFS station with heterogeneous rates whose SCVs are all one (exponential). */
365template <class T>
367 for (std::size_t i = 0; i < sn.nstations; ++i) {
368 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
369 // MATLAB tests `range(sn.rates(i,:)) > 0`, and range's max and min skip
370 // NaN but keep Inf -- unlike sn_has_multi_class_heter_fcfs, which
371 // filters on isfinite. The two predicates really do differ there.
372 bool any = false;
373 double lo = 0.0, hi = 0.0;
374 for (std::size_t r = 0; r < sn.nclasses; ++r) {
375 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
376 const double v = num_traits<T>::to_double(sn.rates(i, r));
377 if (std::isnan(v)) continue;
378 if (!any) {
379 lo = hi = v;
380 any = true;
381 } else {
382 if (v < lo) lo = v;
383 if (v > hi) hi = v;
384 }
385 }
386 if (!(any && hi - lo > 0.0)) continue;
387 // max/min over the scv row also skip NaN, so a disabled pair neither
388 // qualifies nor disqualifies the station; an all-disabled row leaves
389 // max([]) empty and the reference's `if` false, hence `seen`.
390 bool allone = true, seen = false;
391 for (std::size_t r = 0; r < sn.nclasses && allone; ++r) {
392 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
393 const double c = num_traits<T>::to_double(sn.scv(i, r));
394 if (std::isnan(c)) continue;
395 seen = true;
396 if (!(c < 1.0 + qn::GlobalConstants::FineTol && c > 1.0 - qn::GlobalConstants::FineTol))
397 allone = false;
398 }
399 if (seen && allone) return true;
400 }
401 return false;
402}
403
404/** The disciplines the reference admits in sn_has_product_form. */
405template <class T>
407 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
408 const qn::SchedStrategy s = sn.stations[i].sched;
409 if (!(s == qn::SchedStrategy::INF || s == qn::SchedStrategy::PS ||
410 s == qn::SchedStrategy::FCFS || s == qn::SchedStrategy::LCFSPR ||
411 s == qn::SchedStrategy::LCFS || s == qn::SchedStrategy::EXT))
412 return false;
413 }
414 return true;
415}
416
417/** Defined below, after sn_is_mm1k_loss, which it exempts. */
418template <class T>
420
421/**
422 * Port of sn_has_product_form.
423 *
424 * `NetworkStruct::has_product_form` carries the same conjuncts, the fork-join and
425 * state-dependent-routing ones included, so the two answer alike.
426 */
427template <class T>
429 // BCMP asks for infinite buffers. Nothing here read sn.cap/sn.classcap/
430 // sn.droprule, so a BAS-blocked station or any binding finite buffer passed the
431 // gate and the network read as product form while its truncation couples the
432 // station occupancies.
435 !sn_has_blocking(sn) && sn.has_exponential_fcfs();
436}
437
438/**
439 * Port of sn_has_product_form_not_het_fcfs: LCFS is out, and every enabled
440 * FCFS pair with a finite positive SCV must have that SCV equal to one, with
441 * the service means agreeing across the classes served there.
442 *
443 * CHECK_MEANS drops that second half; pass false only for an algorithm that
444 * models class-dependent FCFS itself (ab, schmidt, schmidt-ext).
445 */
446template <class T>
447bool sn_has_product_form_not_het_fcfs(const qn::NetworkStruct<T>& sn, bool check_means = true) {
448 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
449 const qn::SchedStrategy s = sn.stations[i].sched;
450 if (!(s == qn::SchedStrategy::INF || s == qn::SchedStrategy::PS ||
451 s == qn::SchedStrategy::FCFS || s == qn::SchedStrategy::LCFSPR ||
452 s == qn::SchedStrategy::EXT))
453 return false;
454 }
455 if (sn_has_priorities(sn) || sn_has_fork_join(sn) || sn_has_sd_routing(sn)) return false;
456 for (std::size_t i = 0; i < sn.nstations; ++i) {
457 if (sn.stations[i].sched != qn::SchedStrategy::FCFS) continue;
458 // BCMP type 1 asks the FCFS service to be exponential AND
459 // class-independent, so the means are checked alongside the SCVs: with
460 // unequal means the product-form solve returns a wait proportional to
461 // each class's own demand where FCFS makes every class wait behind the
462 // same queue. The mean comparison is between CHAIN service times
463 // (visit-weighted over the classes that actually visit the station): a
464 // class that never visits cannot break product form, and within-chain
465 // heterogeneity is invisible to both the product-form and the qd
466 // branch, which deaggregate a chain result proportionally to each
467 // class's own demand, so only between-chain heterogeneity warrants the
468 // divert. LN layers carry seeded rates for classes with zero visits,
469 // which a raw per-class comparison mistakes for heterogeneity.
470 for (std::size_t r = 0; r < sn.nclasses; ++r) {
471 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
472 const double v = num_traits<T>::to_double(sn.scv(i, r));
473 if (std::isfinite(v) && v > 0.0 &&
475 return false;
476 }
477 if (!check_means || sn.visits.empty()) continue;
478 const std::size_t isf = sn.stateful_of_station(i + 1) - 1;
479 double stmin = 0.0, stmax = 0.0;
480 bool anyserved = false;
481 for (std::size_t c = 0; c < sn.nchains && c < sn.visits.size(); ++c) {
482 double num = 0.0, den = 0.0;
483 for (std::size_t r = 0; r < sn.nclasses; ++r) {
484 if (!sn.chains.empty() && !sn.chains[c][r]) continue;
485 if (!sn.disabled.empty() && sn.disabled[i][r]) continue;
486 const double w = num_traits<T>::to_double(sn.visits[c](isf, r));
487 const double rate = num_traits<T>::to_double(sn.rates(i, r));
488 if (w > qn::GlobalConstants::Zero && std::isfinite(rate) && rate > 0.0) {
489 num += w / rate;
490 den += w;
491 }
492 }
493 if (den > 0.0) {
494 const double st = num / den;
495 if (!anyserved) {
496 stmin = stmax = st;
497 anyserved = true;
498 } else {
499 stmin = std::min(stmin, st);
500 stmax = std::max(stmax, st);
501 }
502 }
503 }
504 if (anyserved && stmax - stmin > qn::GlobalConstants::CoarseTol * stmax) return false;
505 }
506 return true;
507}
508
509/**
510 * `sn_is_population_model`: every station is population dependent only, which
511 * is what lets the fluid and mean-field limits close on the queue lengths.
512 */
513template <class T>
515 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
516 const qn::SchedStrategy s = sn.stations[i].sched;
517 if (!(s == qn::SchedStrategy::INF || s == qn::SchedStrategy::PS ||
518 s == qn::SchedStrategy::PSPRIO || s == qn::SchedStrategy::DPS ||
519 s == qn::SchedStrategy::GPS || s == qn::SchedStrategy::GPSPRIO ||
520 s == qn::SchedStrategy::DPSPRIO || s == qn::SchedStrategy::EXT))
521 return false;
522 }
524}
525
526/**
527 * `sn_is_bas_model`: a single-class closed model with blocking-after-service.
528 */
529template <class T>
531 if (sn.nclasses != 1) return false;
532 double closed = 0.0;
533 for (std::size_t k = 0; k < sn.classes.size(); ++k) {
534 if (std::isinf(sn.classes[k].population)) return false; // open class present
535 closed += sn.classes[k].population;
536 }
537 if (!(closed > 0.0)) return false;
538 // `sn.droprule`, the REFRESHED (nstations x nclasses) table, not the
539 // per-station user declaration: refresh_bas_blocking propagates a
540 // destination-declared BAS rule into the table, and reading the
541 // declaration would miss exactly those models
542 for (std::size_t i = 0; i < sn.droprule.size(); ++i)
543 for (std::size_t r = 0; r < sn.droprule[i].size(); ++r)
544 if (sn.droprule[i][r] == qn::DropStrategy::BAS) return true;
545 return false;
546}
547
548/**
549 * `sn_is_mm1k_loss`: the three-node Source/Queue/Sink model of an M/M/1/K with
550 * loss, the shape MVA answers in closed form instead of iterating.
551 */
552template <class T>
554 return sn.is_mm1k_loss();
555}
556
557/**
558 * Port of sn_has_blocking: some station can REFUSE a job.
559 *
560 * Either its own buffer binds -- Kendall's K below the population that can
561 * reach it, whatever the drop rule (WAITQ, DROP, BAS, BBS, RSRD) -- or a finite
562 * capacity region caps a set of stations jointly. Such a network is not product
563 * form: the truncation couples the station occupancies, so no BCMP
564 * factorization of the equilibrium distribution exists.
565 *
566 * Only a buffer that can actually BIND counts, which is what
567 * sn_get_buffer_size decides; refresh_capacity derives a finite classcap (the
568 * chain population) at every station of every closed model, so a plain
569 * finiteness test would call every closed model blocking.
570 *
571 * A Cache is exempt: it builds its own capped retrieval queues (classcap 1),
572 * which the cache analyzers solve rather than treat as a buffer constraint. So
573 * is the single-station M/M/1/K loss system, whose truncated geometric
574 * distribution is a product form over its one station.
575 */
576template <class T>
578 return sn.has_blocking();
579}
580
581/**
582 * Port of sn_is_fluid_place: node `ind` (1-based, as in the reference) is a FLUID place (Place.setFluid), i.e.
583 * holds one real level instead of a token count per class. False for an out-of-range index.
584 */
585template <class T>
586bool sn_is_fluid_place(const qn::NetworkStruct<T>& sn, std::size_t ind) {
587 return sn.is_fluid_node(ind);
588}
589
590/**
591 * Port of sn_has_fluid_places: the model is a Fluid Stochastic Petri Net, holding a fluid place or a transition
592 * mode with a fluid arc (setFluidRate, setFluidGuard, setFluidImpulse, setFluidRateDependence,
593 * setFluidFlowDependence).
594 */
595template <class T>
597 return sn.has_fluid_places();
598}
599
600} // namespace api
601} // namespace line
602
603#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_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_has_homogeneous_scheduling(const qn::NetworkStruct< T > &sn, qn::SchedStrategy strategy)
Port of sn_has_homogeneous_scheduling: true when EVERY station uses strategy, the reference's length(...
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_quorum_join(const qn::NetworkStruct< T > &sn)
Port of sn_has_quorum_join: true when some Join node declares a non-STD strategy with a positive requ...
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_is_fluid_place(const qn::NetworkStruct< T > &sn, std::size_t ind)
Port of sn_is_fluid_place: node ind (1-based, as in the reference) is a FLUID place (Place....
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_fluid_places(const qn::NetworkStruct< T > &sn)
Port of sn_has_fluid_places: the model is a Fluid Stochastic Petri Net, holding a fluid place or a tr...
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:185
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
Definition lang_types.h:396
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:765
static constexpr double Zero
Definition lang_types.h:767
static constexpr double CoarseTol
Definition lang_types.h:766
The DECLARED join rule of a Join node, by 1-based node index.