LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fluid_mfq_prio.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_PRIO_H
6#define LINE_SOLVERS_FLUID_FLUID_MFQ_PRIO_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The priority branch of the `mfq` method: a port of `solver_mfq_prio.m`.
12 *
13 * WHEN IT IS REACHED. `solver_fluid_analyzer.m` sends a single-queue open model
14 * to `solver_mfq` when every class has the same priority and HERE when they do
15 * not. The model is the same one `mfq` solves -- Source -> Queue -> Sink -- but
16 * the server now drains the highest-priority fluid first, preemptively, so the
17 * per-class levels no longer follow from one fluid-fluid queue.
18 *
19 * WHAT IS SOLVED. Each class arrives as a MAP. The per-class background chains
20 * are SUPERPOSED into one joint chain by a Kronecker sum, and the joint chain
21 * modulates a K x N rate matrix, one row per class. That pair (Qjoint, Rjoint)
22 * with the constant drain rate d is exactly the input of the fluid priority
23 * queue of G. Horvath, "Efficient analysis of the MMAP[K]/PH[K]/1 priority
24 * queue", EJOR 246(1):128-139, 2015, already ported as `mam::mfq_prio_queue`.
25 * The fluid level of a class is read as its queue length and the fluid sojourn
26 * as its response time, the same reading `solver_mfq.m` makes.
27 *
28 * THE ROW ORDER MATTERS AND IS INVERTED. LINE's `classprio` is a RANK: the
29 * SMALLER the value the HIGHER the priority. FluidPrioQueue takes the opposite
30 * convention, the LAST row being the highest priority. The reference therefore
31 * sorts the open classes by `classprio` DESCENDING before building the rows,
32 * and this port keeps that sort stable so ties preserve class order.
33 *
34 * WHEN IT DECLINES. The reference falls back to the matrix fluid method, with a
35 * warning, whenever the fluid priority model degenerates: a non-MAP arrival,
36 * class-dependent or non-positive service, or a joint chain with a single state
37 * (unmodulated arrivals, where the fluid level is identically zero and the
38 * priority structure carries no information). This port reports the same
39 * decision through `MfqPrioResult::fallback` and lets the caller re-dispatch,
40 * rather than returning zeros that look like an answer.
41 *
42 * A FLUID LEVEL IS NOT A CUSTOMER COUNT. The level is zero unless the ARRIVAL
43 * RATE EXCEEDS d in some background state; a class whose peak rate stays below
44 * the drain rate reports QN = 0 exactly, which is correct for the fluid model
45 * and is not a failure of the solve.
46 */
47
48#include <algorithm>
49#include <cmath>
50#include <cstddef>
51#include <numeric>
52#include <string>
53#include <vector>
54
59#include "line/util/error.h"
60#include "line/util/matrix.h"
61
62namespace line {
63namespace fluid {
64
65/** Per-class metrics of the priority queue, indexed by class. */
67 std::vector<double> QN, RN, TN, UN;
68 bool fallback = false; ///< true when the reference would run the matrix method instead
69 std::string reason; ///< why, for the caller's warning
70};
71
72namespace detail {
73
74/** Kronecker sum term: kron over j of (j == i ? A_i : I_{n_j}), in j order. */
75inline Matrix<double> prio_kron_term(const std::vector<Matrix<double>>& A,
76 const std::vector<std::size_t>& n, std::size_t i) {
77 Matrix<double> term(1, 1, 1.0);
78 for (std::size_t j = 0; j < A.size(); ++j) {
79 if (j == i)
80 term = mam::kron(term, A[i]);
81 else
82 term = mam::kron(term, line::eye<double>(n[j]));
83 }
84 return term;
85}
86
87} // namespace detail
88
89/**
90 * Solve the single priority fluid queue of `sn`.
91 *
92 * @param top the single-queue topology, as `mfq_is_single_queue` found it
93 * @param tol `options.tol`, passed to the Riccati iterations as their precision
94 * @param sn the refreshed network struct
95 */
96template <class T>
98 const std::size_t K = sn.nclasses;
99 if (!top.ok)
100 throw UnsupportedError(
101 "fluid mfq: the priority fluid queue needs a model of open classes flowing "
102 "Source -> Queue -> Sink and nothing else");
103 MfqPrioResult out;
104 out.QN.assign(K, 0.0);
105 out.RN.assign(K, 0.0);
106 out.TN.assign(K, 0.0);
107 out.UN.assign(K, 0.0);
108
109 // Open classes, ordered so that the LAST row is the highest priority.
110 std::vector<std::size_t> ord = top.open_classes;
111 std::stable_sort(ord.begin(), ord.end(), [&sn](std::size_t a, std::size_t b) {
112 return sn.classes[a].prio > sn.classes[b].prio;
113 });
114 const std::size_t Kc = ord.size();
115 if (Kc == 0) {
116 out.fallback = true;
117 out.reason = "invalid service rates";
118 return out;
119 }
120
121 // The drain rate must be one number: a priority fluid queue has ONE server.
122 std::vector<double> mu(Kc, 0.0);
123 for (std::size_t i = 0; i < Kc; ++i)
124 mu[i] = num_traits<T>::to_double(sn.rates(top.queue, ord[i]));
125 for (std::size_t i = 0; i < Kc; ++i)
126 if (!std::isfinite(mu[i]) || mu[i] <= 0.0) {
127 out.fallback = true;
128 out.reason = "invalid service rates";
129 return out;
130 }
131 const double fine_tol = 1e-8; // GlobalConstants.FineTol
132 for (std::size_t i = 0; i < Kc; ++i)
133 if (std::fabs(mu[i] - mu[0]) > fine_tol * std::max(1.0, mu[0])) {
134 out.fallback = true;
135 out.reason = "class-dependent service";
136 return out;
137 }
138 const double d = mu[0];
139
140 // Per-class arrival fluid: Q_k = D0 + D1 and the rate vector R_k = D1 e.
141 std::vector<Matrix<double>> Qk(Kc), Rk(Kc);
142 std::vector<std::size_t> Nk(Kc, 0);
143 std::vector<double> lambda(Kc, 0.0);
144 for (std::size_t i = 0; i < Kc; ++i) {
145 const lang::Distrib<T>& arr = sn.service[top.source][ord[i]];
146 const std::size_t n = arr.D0.rows();
147 if (n == 0 || arr.D1.rows() != n) {
148 out.fallback = true;
149 out.reason = "non-MAP arrival";
150 return out;
151 }
152 Qk[i] = Matrix<double>(n, n, 0.0);
153 Rk[i] = Matrix<double>(n, 1, 0.0);
154 for (std::size_t a = 0; a < n; ++a)
155 for (std::size_t b = 0; b < n; ++b) {
156 Qk[i](a, b) = num_traits<T>::to_double(arr.D0(a, b)) +
157 num_traits<T>::to_double(arr.D1(a, b));
158 Rk[i](a, 0) += num_traits<T>::to_double(arr.D1(a, b));
159 }
160 Nk[i] = n;
161 lambda[i] = num_traits<T>::to_double(sn.rates(top.source, ord[i]));
162 }
163
164 std::size_t Njoint = 1;
165 for (std::size_t i = 0; i < Kc; ++i) Njoint *= Nk[i];
166 if (Njoint < 2) {
167 out.fallback = true;
168 out.reason = "non-modulated (exponential) arrivals";
169 return out;
170 }
171
172 // The joint background chain, and the per-class rate in each of its states.
173 Matrix<double> Qjoint(Njoint, Njoint, 0.0);
174 for (std::size_t i = 0; i < Kc; ++i) {
175 const Matrix<double> term = detail::prio_kron_term(Qk, Nk, i);
176 for (std::size_t a = 0; a < Njoint; ++a)
177 for (std::size_t b = 0; b < Njoint; ++b) Qjoint(a, b) += term(a, b);
178 }
179 Matrix<double> Rjoint(Kc, Njoint, 0.0);
180 std::vector<Matrix<double>> ones_col(Kc);
181 for (std::size_t j = 0; j < Kc; ++j) ones_col[j] = Matrix<double>(Nk[j], 1, 1.0);
182 for (std::size_t i = 0; i < Kc; ++i) {
183 Matrix<double> v(1, 1, 1.0);
184 for (std::size_t j = 0; j < Kc; ++j) v = mam::kron(v, (j == i) ? Rk[i] : ones_col[j]);
185 for (std::size_t a = 0; a < Njoint; ++a) Rjoint(i, a) = v(a, 0);
186 }
187
189 po.prec = (tol > 0.0) ? tol : 1e-14;
190 po.classes.clear();
191 for (std::size_t i = 1; i <= Kc; ++i) po.classes.push_back(i);
193 try {
194 po.flMoms = 1;
195 po.stMoms = 0;
196 fl = mam::mfq_prio_queue(Qjoint, Rjoint, d, po);
197 po.flMoms = 0;
198 po.stMoms = 1;
199 st = mam::mfq_prio_queue(Qjoint, Rjoint, d, po);
200 } catch (const std::exception& e) {
201 out.fallback = true;
202 out.reason = e.what();
203 return out;
204 }
205
206 for (std::size_t i = 0; i < Kc; ++i) {
207 const std::size_t k = ord[i];
208 out.QN[k] = (i < fl.flMoms.size() && !fl.flMoms[i].empty()) ? fl.flMoms[i][0] : 0.0;
209 out.RN[k] = (i < st.stMoms.size() && !st.stMoms[i].empty()) ? st.stMoms[i][0] : 0.0;
210 out.TN[k] = lambda[i];
211 out.UN[k] = std::min(1.0, lambda[i] / d);
212 }
213 return out;
214}
215
216} // namespace fluid
217} // namespace line
218
219#endif // LINE_SOLVERS_FLUID_FLUID_MFQ_PRIO_H
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
The exception types the port throws.
The mfq method: a port of solver_mfq.m and the single-queue gate fluid_is_single_queue....
Dense matrix and non-owning view.
Fluid priority queue: per-class fluid level and sojourn time of an MMAP[K]/PH[K]/1-type continuous fl...
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
MfqPrioResult fluid_mfq_prio(const qn::NetworkStruct< T > &sn, const MfqTopology &top, double tol)
Solve the single priority fluid queue of sn.
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Definition mmap_lambda.h:57
FluidPrioResult mfq_prio_queue(const Matrix< double > &Q, const Matrix< double > &R, double d, const FluidPrioOptions &opt)
Fluid priority queue.
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
A queueing network and its refreshed NetworkStruct.
Per-class metrics of the priority queue, indexed by class.
std::string reason
why, for the caller's warning
std::vector< double > RN
std::vector< double > QN
std::vector< double > UN
bool fallback
true when the reference would run the matrix method instead
std::vector< double > TN
What the single-queue gate found, when it matches.
Definition fluid_mfq.h:54
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
Which measures to compute, and the numerical options.
std::size_t flMoms
number of fluid level moments, 0 = not wanted
std::size_t stMoms
number of sojourn time moments
double prec
Riccati and matrix-quadratic tolerance.
std::vector< std::size_t > classes
1-based classes to analyze, empty = all
One entry per analyzed class, in the order given by FluidPrioOptions::classes.
std::vector< std::vector< double > > flMoms
fluid level moments per class
std::vector< std::vector< double > > stMoms
sojourn time moments per class