LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fluid_mfq_admits.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_FLUID_FLUID_MFQ_ADMITS_H
6#define LINE_SOLVERS_FLUID_FLUID_MFQ_ADMITS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `fluid_mfq_admits.m` and `fluid_is_single_queue.m`: can the
12 * `mfq` fluid method (and its alias `aoi`) run as itself on this model?
13 *
14 * The Markovian fluid queue answers ONE open station, Source -> Queue -> Sink.
15 * Off that shape `mfq` RESOLVES to `matrix` (`SolverFLD.resolveMethod`), which
16 * is the fallback the analyzer has always taken: stating it before the gate is
17 * what makes the gate check, and the result report, the method that answers.
18 * One predicate, two callers (`fluid_resolve_method` and the `mfq` arm of
19 * `fluid_dispatch`), as in MATLAB and native python.
20 *
21 * Three arms, tried in the analyzer's own order:
22 * - the age-of-information shape (`aoi_is_aoi`);
23 * - the priority branch, taken when the classes carry distinct priorities: a
24 * class-independent finite positive service rate, a MAP {D0,D1} arrival for
25 * every open class, at least one of them modulated;
26 * - the plain branch, stated for ONE open class.
27 * Closed classes are refused by the feature set rather than here.
28 *
29 * The topology test is NODE-level, as the reference's is: a Delay or Router
30 * node does not disqualify the model, only a second Source, Queue or Sink
31 * does. That differs from `mfq_is_single_queue`, the topology the solver
32 * itself builds, which is stricter; a model admitted here and refused there is
33 * answered by `matrix` in the dispatch, exactly as before.
34 */
35
36#include <algorithm>
37#include <cmath>
38#include <cstddef>
39#include <set>
40#include <string>
41#include <vector>
42
45
46namespace line {
47namespace fluid {
48
49/** The single-queue topology the reference reads, in 0-based indices. */
51 bool ok = false;
52 std::string error; ///< the reference's errorMsg when !ok
53 std::size_t source = 0; ///< 0-based station index of the Source
54 std::size_t queue = 0; ///< 0-based station index of the Queue
55};
56
57/** Port of `fluid_is_single_queue.m`. */
58template <class T>
61 const std::size_t K = sn.nclasses;
62 bool any_open = false;
63 for (std::size_t r = 0; r < K; ++r)
64 if (!std::isfinite(sn.classes[r].population)) any_open = true;
65 if (!any_open) {
66 t.error = "Not an open model - all classes are closed";
67 return t;
68 }
69 std::size_t nsrc = 0, nq = 0, nsink = 0, qnode = 0, srcnode = 0;
70 for (std::size_t ind = 0; ind < sn.nodes.size(); ++ind) {
71 const qn::NodeType nt = sn.nodes[ind].nodetype;
72 if (nt == qn::NodeType::Source) { ++nsrc; srcnode = ind + 1; }
73 else if (nt == qn::NodeType::Queue) { ++nq; qnode = ind + 1; }
74 else if (nt == qn::NodeType::Sink) ++nsink;
75 }
76 if (nsrc == 0) { t.error = "No source node found"; return t; }
77 if (nsrc > 1) { t.error = "Multiple source nodes found (" + std::to_string(nsrc) + ")"; return t; }
78 if (nsink == 0) { t.error = "No sink node found"; return t; }
79 if (nsink > 1) { t.error = "Multiple sink nodes found (" + std::to_string(nsink) + ")"; return t; }
80 if (nq == 0) { t.error = "No queue node found"; return t; }
81 if (nq > 1) {
82 t.error = "Multiple queue nodes found (" + std::to_string(nq) +
83 ") - MFQ supports single queue only";
84 return t;
85 }
86 const std::size_t sst = sn.nodes[srcnode - 1].station; // 1-based, 0 = not a station
87 const std::size_t qst = sn.nodes[qnode - 1].station;
88 if (sst == 0 || qst == 0) {
89 t.error = "MFQ requires source and queue to be stations";
90 return t;
91 }
92 const double c = sn.stations[qst - 1].nservers;
93 if (c > 1 && std::isfinite(c)) {
94 t.error = "Multi-server queue (c=" + std::to_string(static_cast<long long>(c)) +
95 ") not supported - MFQ requires c=1 or c=Inf";
96 return t;
97 }
98 // No self-loop at the queue. rt is over the STATEFUL nodes; the reference
99 // indexes it with the station index, which coincides on this shape.
100 const auto it = std::lower_bound(sn.stateful_nodes.begin(), sn.stateful_nodes.end(), qnode);
101 if (it != sn.stateful_nodes.end() && *it == qnode) {
102 const std::size_t isf = static_cast<std::size_t>(it - sn.stateful_nodes.begin());
103 for (std::size_t k = 0; k < K; ++k) {
104 const std::size_t idx = isf * K + k;
105 if (idx < sn.rt.rows() && idx < sn.rt.cols() &&
106 num_traits<T>::to_double(sn.rt(idx, idx)) > 0) {
107 t.error = "Self-loop detected at queue - violates independence assumption";
108 return t;
109 }
110 }
111 }
112 t.source = sst - 1;
113 t.queue = qst - 1;
114 t.ok = true;
115 return t;
116}
117
118/** Verdict of `fluid_mfq_admits`. */
120 bool ok = false;
121 std::string reason; ///< the refusal, empty when ok
122 bool is_aoi = false; ///< the age-of-information arm is the one that runs
123};
124
125/** Port of `fluid_mfq_admits.m`. */
126template <class T>
129 if (aoi_is_aoi(sn).ok) {
130 a.ok = true;
131 a.is_aoi = true;
132 return a;
133 }
135 if (!top.ok) {
136 a.reason = "The 'mfq' method is a single-queue fluid model (Source -> Queue -> Sink, "
137 "one or infinitely many servers): " + top.error + ".";
138 return a;
139 }
140 std::vector<std::size_t> open;
141 for (std::size_t r = 0; r < sn.nclasses; ++r)
142 if (!std::isfinite(sn.classes[r].population)) open.push_back(r);
143 // the same test the analyzer switches on: distinct priorities over ALL classes
144 std::set<double> prios;
145 for (std::size_t r = 0; r < sn.nclasses; ++r) prios.insert(static_cast<double>(sn.classes[r].prio));
146 if (prios.size() > 1) {
147 const char* rate_msg = "The priority branch of 'mfq' needs a finite positive service rate "
148 "for every open class.";
149 if (open.empty()) { a.reason = rate_msg; return a; }
150 std::vector<double> mu;
151 for (std::size_t k : open) mu.push_back(num_traits<T>::to_double(sn.rates(top.queue, k)));
152 for (double m : mu)
153 if (!std::isfinite(m) || m <= 0) { a.reason = rate_msg; return a; }
154 const double fine_tol = 1e-8; // GlobalConstants.FineTol
155 for (double m : mu)
156 if (std::fabs(m - mu[0]) > fine_tol * std::max(1.0, mu[0])) {
157 a.reason = "The priority branch of 'mfq' is a fluid priority queue drained at one "
158 "rate, so it needs a class-independent service rate.";
159 return a;
160 }
161 std::size_t nph = 1;
162 for (std::size_t k : open) {
163 const lang::Distrib<T>& arr = sn.service[top.source][k];
164 const std::size_t n = arr.D0.rows();
165 if (n == 0 || arr.D1.rows() != n) {
166 a.reason = "The priority branch of 'mfq' needs a MAP {D0,D1} arrival process for "
167 "class " + std::to_string(k + 1) + ".";
168 return a;
169 }
170 nph *= n;
171 }
172 if (nph < 2) {
173 a.reason = "The priority branch of 'mfq' needs at least one Markov-modulated "
174 "(multi-phase) arrival process: with exponential arrivals its fluid model "
175 "degenerates.";
176 return a;
177 }
178 a.ok = true;
179 return a;
180 }
181 if (open.size() != 1) {
182 a.reason = "The 'mfq' method reads the arrival and service processes of one open class "
183 "and this model has " + std::to_string(open.size()) +
184 "; several classes are served only by its priority branch (distinct class "
185 "priorities).";
186 return a;
187 }
188 a.ok = true;
189 return a;
190}
191
192} // namespace fluid
193} // namespace line
194
195#endif // LINE_SOLVERS_FLUID_FLUID_MFQ_ADMITS_H
A network plus its refreshed NetworkStruct.
Age of Information by Markovian fluid queues: a port of solver_mfq_aoi.m (identical to solver_fluid_a...
FluidSingleQueue fluid_is_single_queue(const qn::NetworkStruct< T > &sn)
Port of fluid_is_single_queue.m.
FluidMfqAdmits fluid_mfq_admits(const qn::NetworkStruct< T > &sn)
Port of fluid_mfq_admits.m.
AoiTopology aoi_is_aoi(const qn::NetworkStruct< T > &sn)
Port of aoi_is_aoi.m.
Definition fluid_aoi.h:102
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:331
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Verdict of fluid_mfq_admits.
bool is_aoi
the age-of-information arm is the one that runs
std::string reason
the refusal, empty when ok
The single-queue topology the reference reads, in 0-based indices.
std::size_t source
0-based station index of the Source
std::string error
the reference's errorMsg when !ok
std::size_t queue
0-based station index of the Queue
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
Definition lang_types.h:863