LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fluid_mfq.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_H
6#define LINE_SOLVERS_FLUID_FLUID_MFQ_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The `mfq` method: a port of `solver_mfq.m` and the single-queue gate
12 * `fluid_is_single_queue.m`.
13 *
14 * WHAT MAKES THIS DIFFERENT FROM EVERY OTHER FLUID METHOD HERE. The others
15 * approximate a network by following the mean drift of its queues. This one is
16 * EXACT, and only works on one queue: a Source feeding a single station whose
17 * arrival and service processes are Markov-modulated fluids. It solves the
18 * fluid queue analytically rather than integrating anything, which is why it
19 * carries a topology gate instead of a tolerance -- there is nothing to
20 * converge.
21 *
22 * WHAT IT SOLVES. Both processes are given as (D0, D1) pairs and converted to
23 * BuTools fluid form: the background generator is Q = D0 + D1 and the fluid
24 * rate in each phase is the row sum of D1. `mfq_fluflu_sojourn` (already ported
25 * from BuTools' FluFluQueue) then returns a matrix-exponential representation
26 * of the sojourn time, whose mean is the response time. Queue length follows by
27 * Little's law, which is exact in steady state, and throughput is the arrival
28 * rate the queue is stable under.
29 *
30 * THE M/M/1 SHORT CIRCUIT is the reference's. With a single-phase arrival and a
31 * single-phase service there is no modulation left, the fluid machinery is
32 * degenerate, and the reference falls back to the textbook formulas
33 * L = rho/(1-rho), W = 1/(mu - lambda). Reproduced, including the instability
34 * check: at rho >= 1 the queue has no stationary distribution and the reference
35 * reports infinities rather than a finite wrong answer.
36 */
37
38#include <cmath>
39#include <cstddef>
40#include <limits>
41#include <vector>
42
46#include "line/util/error.h"
47#include "line/util/lu.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace fluid {
52
53/** What the single-queue gate found, when it matches. */
55 bool ok = false;
56 std::size_t source = 0; ///< 0-based station index
57 std::size_t queue = 0;
58 std::size_t cls = 0; ///< the first open class, which `mfq` analyzes
59 std::vector<std::size_t> open_classes; ///< every open class, in class order
60};
61
62/**
63 * Port of `fluid_is_single_queue.m`: the model must be one open class flowing
64 * Source -> Queue -> Sink and nothing else.
65 */
66template <class T>
69 const std::size_t M = sn.nstations, K = sn.nclasses;
70 std::size_t nsrc = 0, nq = 0, nsink = 0;
71 for (std::size_t i = 0; i < M; ++i) {
72 switch (sn.stations[i].nodetype) {
73 case qn::NodeType::Source: ++nsrc; t.source = i; break;
74 case qn::NodeType::Sink: ++nsink; break;
75 case qn::NodeType::Queue: ++nq; t.queue = i; break;
76 default: return t; // a Delay, Cache or anything else disqualifies
77 }
78 }
79 // A Sink is a node but not always a station in this port, so it is
80 // counted when present and not required.
81 (void)nsink;
82 if (nsrc != 1 || nq != 1) return t;
83 // The reference requires AT LEAST one open class, not exactly one: a
84 // multiclass single queue is where the priority branch takes over.
85 for (std::size_t r = 0; r < K; ++r) {
86 if (std::isfinite(sn.classes[r].population)) return t; // closed class
87 t.open_classes.push_back(r);
88 }
89 if (t.open_classes.empty()) return t;
90 t.cls = t.open_classes[0];
91 t.ok = true;
92 return t;
93}
94
95/** The metrics `mfq` reports for its single queue. */
96struct MfqResult {
97 double QN = 0.0, RN = 0.0, TN = 0.0, UN = 0.0;
98 bool unstable = false;
99};
100
101namespace detail {
102
103/** Port of `fluid_dist2butools`: Q = D0 + D1, R = diag(row sums of D1). */
104template <class T>
105void mfq_dist2butools(const lang::Distrib<T>& d, Matrix<double>& Q, Matrix<double>& R) {
106 const std::size_t n = d.D0.rows();
107 Q = Matrix<double>(n, n, 0.0);
108 R = Matrix<double>(n, n, 0.0);
109 for (std::size_t a = 0; a < n; ++a) {
110 double row = 0.0;
111 for (std::size_t b = 0; b < n; ++b) {
112 Q(a, b) = num_traits<T>::to_double(d.D0(a, b)) + num_traits<T>::to_double(d.D1(a, b));
113 row += num_traits<T>::to_double(d.D1(a, b));
114 }
115 R(a, a) = row;
116 }
117}
118
119/**
120 * Mean of a matrix-exponential representation: -alpha A^-1 e.
121 *
122 * The sojourn representation is (alpha, A) with density alpha exp(A t) (-A e),
123 * so the first moment is -alpha A^-1 e; solving against A is what avoids
124 * forming the inverse.
125 */
126inline double mfq_me_mean(const mam::MeRepresentation<double>& me) {
127 const std::size_t n = me.A.rows();
128 if (n == 0) return 0.0;
129 std::vector<double> e(n, 1.0);
130 // Solve A y = e, then the mean is -alpha . y.
131 Matrix<double> A = me.A;
132 const std::vector<std::size_t> piv = lu_factor(A);
133 std::vector<double> y = e;
134 lu_solve(A, piv, y);
135 double s = 0.0;
136 for (std::size_t i = 0; i < n && i < me.alpha.size(); ++i) s += me.alpha[i] * y[i];
137 return -s;
138}
139
140} // namespace detail
141
142/** Solve the single fluid queue of `sn`. */
143template <class T>
144MfqResult fluid_mfq(const qn::NetworkStruct<T>& sn, const MfqTopology& top, double tol) {
145 if (!top.ok)
146 throw UnsupportedError(
147 "fluid mfq: the method solves a single Markov-modulated fluid queue and needs a model "
148 "of exactly one open class flowing Source -> Queue -> Sink");
149 const lang::Distrib<T>& arr = sn.service[top.source][top.cls];
150 const lang::Distrib<T>& svc = sn.service[top.queue][top.cls];
151
152 mam::Map<double> am, sm;
153 const std::size_t na = arr.D0.rows(), ns = svc.D0.rows();
154 am.D0 = Matrix<double>(na, na, 0.0);
155 am.D1 = Matrix<double>(na, na, 0.0);
156 sm.D0 = Matrix<double>(ns, ns, 0.0);
157 sm.D1 = Matrix<double>(ns, ns, 0.0);
158 for (std::size_t a = 0; a < na; ++a)
159 for (std::size_t b = 0; b < na; ++b) {
160 am.D0(a, b) = num_traits<T>::to_double(arr.D0(a, b));
161 am.D1(a, b) = num_traits<T>::to_double(arr.D1(a, b));
162 }
163 for (std::size_t a = 0; a < ns; ++a)
164 for (std::size_t b = 0; b < ns; ++b) {
165 sm.D0(a, b) = num_traits<T>::to_double(svc.D0(a, b));
166 sm.D1(a, b) = num_traits<T>::to_double(svc.D1(a, b));
167 }
168 const double lambda = 1.0 / mam::map_mean(am);
169 const double mu = 1.0 / mam::map_mean(sm);
170
171 MfqResult out;
172 out.TN = lambda;
173
174 Matrix<double> Qin, Rin, Qout, Rout;
175 detail::mfq_dist2butools(arr, Qin, Rin);
176 detail::mfq_dist2butools(svc, Qout, Rout);
177
178 // The degenerate case the reference short-circuits: no modulation left.
179 const bool simple_exp = (na == 1 && ns == 1);
180 if (simple_exp) {
181 const double rho = lambda / mu;
182 if (rho >= 1.0) {
183 out.unstable = true;
184 out.QN = std::numeric_limits<double>::infinity();
185 out.RN = std::numeric_limits<double>::infinity();
186 out.UN = 1.0;
187 return out;
188 }
189 out.QN = rho / (1.0 - rho);
190 out.RN = 1.0 / (mu - lambda);
191 out.UN = rho;
192 return out;
193 }
194
195 // The genuinely Markov-modulated case: solve the fluid-fluid queue.
196 //
197 // A NOTE ON WHAT THIS MEASURES, because it is easy to check against the
198 // wrong formula. This is a FLUID queue: work arrives as a continuous
199 // stream at a Markov-modulated rate and drains at another, so the sojourn
200 // is the delay of a DROP OF FLUID, not a customer's waiting time in the
201 // corresponding M/G/1 system. On M/E2/1 with lambda 0.5 and mean service
202 // 0.5 the two differ by more than tenfold (0.0417 against 0.625), and the
203 // fluid figure is the right one here -- MATLAB's mfq reports exactly
204 // 0.0417 on that model.
206 mam::mfq_fluflu_sojourn(Qin, Rin, Qout, Rout, /*srv0stop=*/true, /*transToPH=*/false,
207 std::max(tol, 1e-14));
208 out.RN = detail::mfq_me_mean(me);
209 out.QN = lambda * out.RN; // Little's law, exact in steady state
210 // The reference reports the fluid LEVEL as the utilization on this branch,
211 // not lambda/mu: the server is busy exactly while there is fluid to drain.
212 out.UN = out.QN;
213 (void)mu;
214 return out;
215}
216
217} // namespace fluid
218} // namespace line
219
220#endif // LINE_SOLVERS_FLUID_FLUID_MFQ_H
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
Sojourn-time distribution of a fluid queue whose SERVICE is itself a Markov-modulated fluid flow,...
MfqTopology mfq_is_single_queue(const qn::NetworkStruct< T > &sn)
Port of fluid_is_single_queue.m: the model must be one open class flowing Source -> Queue -> Sink and...
Definition fluid_mfq.h:67
MfqResult fluid_mfq(const qn::NetworkStruct< T > &sn, const MfqTopology &top, double tol)
Solve the single fluid queue of sn.
Definition fluid_mfq.h:144
MeRepresentation< T > mfq_fluflu_sojourn(const Matrix< T > &Qin, const Matrix< T > &Rin, const Matrix< T > &Qout, const Matrix< T > &Rout, bool srv0stop, bool transToPH, const T &prec)
Sojourn time of a drop in a fluid queue with fluid-modulated service.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition lu.h:94
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
A queueing network and its refreshed NetworkStruct.
The metrics mfq reports for its single queue.
Definition fluid_mfq.h:96
What the single-queue gate found, when it matches.
Definition fluid_mfq.h:54
std::size_t cls
the first open class, which mfq analyzes
Definition fluid_mfq.h:58
std::vector< std::size_t > open_classes
every open class, in class order
Definition fluid_mfq.h:59
std::size_t source
0-based station index
Definition fluid_mfq.h:56
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
Definition lang_types.h:759
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
A matrix-exponential or phase-type representation (alpha, A).
Definition mfq_sojourn.h:70