LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mva_types.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_MVA_MVA_TYPES_H
6#define LINE_SOLVERS_MVA_MVA_TYPES_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The option and result types every MVA analyzer shares.
12 *
13 * They live in their own header so that an analyzer in a separate translation
14 * unit (solver_qna.h, and any analyzer that follows it) can speak the same
15 * contract without including solver_mva.h and forming a cycle.
16 */
17
18#include <cmath>
19#include <optional>
20#include <string>
21#include <vector>
22
26#include "line/util/matrix.h"
27
28namespace line {
29namespace mva {
30
31/** The options SolverMVA reads. Defaults are SolverOptions('MVA'). */
32struct MvaOptions {
33 std::string method = "default";
34 double tol = 1e-4;
35 double iter_tol = 1e-6;
36 int iter_max = 1000;
37 std::string multiserver = "default";
38 std::string highvar = "default";
39 /**
40 * `options.config.np_priority`: which non-preemptive priority correction a
41 * HOL station takes. `default` and `cl` are Chandy-Lakshmi, which discounts
42 * the higher-priority utilization by the Linearizer's throughput
43 * difference; `shadow` is Sevcik's shadow server, which does not.
44 */
45 std::string np_priority = "default";
46 /**
47 * `options.config.fork_join`: which fork-join arm the fixed point takes.
48 * `default`, `mmt` and `fjt` are the MMT transform of fj_mmt.h;
49 * `ht` and `heidelberger-trivedi` are the Heidelberger-Trivedi transform of
50 * fj_ht.h, which is closed-model only. Read only by a model with a Fork.
51 */
52 std::string fork_join = "default";
53 /** Warm start, (nstations x nchains) chain-aggregated queue lengths; empty for none. */
54 bool has_init_sol = false;
55 /**
56 * Whether the model this solve came from has a Fork, which the model handed
57 * to the analyzer no longer does.
58 *
59 * `mvaDispatch.m` tests `self.model.hasFork` -- the BASE model -- to force
60 * `default` onto AMVA, and by the time the analyzer runs the fork-join
61 * transform has already replaced the fork with a router, so the struct in
62 * hand cannot answer the question. Testing the transformed struct instead
63 * silently sends the model to exact mixed MVA, which the auxiliary
64 * near-zero-rate open classes degenerate.
65 */
66 bool base_has_fork = false;
67 /**
68 * `options.config.rqt_regime`: which service adaptation regime of the
69 * Robust Queueing Theory Table 1 the RQT analyzer takes. Empty and
70 * `independent` leave the service distribution unknown; `normal` and
71 * `pareto` are the service-dependent fits.
72 */
73 std::string rqt_regime;
74 /** `options.config.rqt_exact`: use the exact worst case over the uncertainty
75 * sets instead of the closed-form bound of Theorem 3. */
76 bool rqt_exact = false;
77 /** `options.config.rqt_alpha_a`: arrival tail coefficient in (1,2]; 0 = 2. */
78 double rqt_alpha_a = 0.0;
79 /** `options.config.rqt_alpha_s`: service tail coefficient in (1,2]; 0 = 2. */
80 double rqt_alpha_s = 0.0;
81 /**
82 * `options.config.interlock`: the interlocked-flow matrix of Franks (1999),
83 * Eq. (4.7), CLASS-indexed and row-major (nclasses x nclasses). interlock[r][s]
84 * is the share of the class-s queue that a class-r arrival must not see, because
85 * that work was itself caused by the class-r request. Empty for every model but
86 * the layers of SolverLN. It is kept CLASS-indexed, not chain-indexed, so that a
87 * later chain refresh cannot leave it stale: the analyzer aggregates it to chains
88 * against the struct it is about to solve. Doubles rather than T because the
89 * probabilities come from a fixed-point iterate and never enter an exact
90 * computation of their own.
91 */
92 std::vector<std::vector<double>> interlock;
93};
94
95/** Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers. */
96template <class T>
99 std::vector<T> C, X;
100 std::string method;
101 int iter = 0;
102 /**
103 * Whether the fixed point met its tolerance, or empty when the handler
104 * reports none. NOT `iter < iter_max`: `solver_amva` increments `totiter`
105 * inside the nested per-chain and inner loops, so it aggregates inner sweeps
106 * and can reach the cap on a solve whose outer residual is exactly zero.
107 * Only the residual decides there, which is why the flag has to travel with
108 * the solution instead of being recomputed by a caller from the count.
109 */
110 std::optional<bool> converged;
111 /**
112 * log of the normalizing constant, the reference's `lG`.
113 *
114 * Only the exact MVA recursion carries one: an AMVA result has no G, and
115 * the reference returns 0 there rather than a fabricated value, which is
116 * why `getProbNormConstAggr` re-enters the analyzer at method='exact'
117 * instead of reusing whatever the last solve produced.
118 */
119 double lG = 0.0;
120 /** Fork-join section metrics set by the fork-join fixed point (fj_driver.h); empty otherwise. */
122 /**
123 * Analyzer-supplied station arrival rates (M x K), NaN where the runner derives them from the routing; empty
124 * when the analyzer supplies none. The open retrieval analyzers book the fetches to the READ class, which
125 * sn.rt does not route from the Cache, so they set the fetch-station rows from their traffic equations.
126 */
128};
129
130/*
131 * Per-method structural gates, shared by `list_valid_methods` and by the
132 * analyzers themselves.
133 *
134 * ONE predicate with TWO callers. A rule kept in two places is how the report
135 * comes to offer a (solver, method) pair that the analyzer then refuses -- or,
136 * worse, answers with a table of zeros. What the feature registry CAN name lives
137 * in `qn::mva_feature_set` instead; these are the rules it cannot: a product
138 * form, a class count and a server count have no registry name.
139 *
140 * Each returns the refusal sentence, or an empty string when the method may run.
141 */
142
143/**
144 * The closed-population AMVA family: an open chain, or a network outside strict
145 * product form, has nothing for the recursion to work on.
146 *
147 * Open chains are also expressed in the registry (`mva_feature_set` drops
148 * OpenClass for these), which is what keeps them off the report; they are
149 * repeated here because the analyzer must refuse by name with a sentence rather
150 * than fall through to `solver_amvald` and answer under a method nobody asked
151 * for. Strict product form has no registry name at all, so this is its only home.
152 */
153template <class T>
155 const std::string& method) {
156 if (!qn::mva_is_closed_population_method(method)) return "";
157 const std::string base = qn::mva_base_method(method);
158 if (L.has_open_classes())
159 return "solver_amva: the '" + base +
160 "' method approximates the arrival-instant queue length as a function of the "
161 "closed population vector N, so it is defined for closed models only; use "
162 "'default', 'lin', 'qd' or 'qna' for a model with open classes";
163 // ab, schmidt and schmidt-ext ARE the class-dependent FCFS algorithms, so
164 // heterogeneous FCFS service means are their subject matter rather than a
165 // disqualification.
166 const bool check_means = !(base == "ab" || base == "schmidt" || base == "schmidt-ext");
167 if (L.has_product_form_not_het_fcfs(check_means)) return "";
168 return "solver_amva: the '" + base +
169 "' method is defined for strict product-form, load-independent models; use 'default', "
170 "'lin' or 'qd' for this model";
171}
172
173/**
174 * RQNA and RQT decompose an open network into GI/G/1 queues and build one
175 * uncertainty set per flow out of the first two moments of a SINGLE stream, so a
176 * multiclass model has no counterpart in their equations. No registry feature
177 * names a class count, so that half of the rule is structural.
178 *
179 * A FORK-JOIN model is refused too. A Join is a synchronisation node, not a
180 * queue: it carries no service process, so the index-of-dispersion curve these
181 * analyzers read off every station does not exist for it, and neither has a
182 * synchronisation term to put in its place. That half IS nameable, so
183 * `qn::mva_feature_set` drops Fork/Join for these two as well and this is the
184 * analyzer's half of it.
185 */
186template <class T>
188 const std::string& method) {
189 const std::string base = qn::mva_base_method(method);
190 if (base != "rqna" && base != "rqt") return "";
191 const std::string label = (base == "rqna") ? "RQNA" : "RQT";
192 // Scanned over the NODES and not the stations: a Fork is not a station in
193 // this port, so a station scan would see only the Join.
194 for (const qn::NodeDef& nd : L.nodes)
195 if (nd.nodetype == qn::NodeType::Fork || nd.nodetype == qn::NodeType::Join)
196 return "solver_" + base + ": " + label +
197 " decomposes an open network into GI/G/1 queues and has no synchronisation "
198 "term; a Join carries no service process for its index of dispersion to be "
199 "read from; use the 'default' method for a fork-join model";
200 if (L.nclasses == 1) return "";
201 return "solver_" + base + ": " + label +
202 " supports single-class open networks only; use method 'qna' for multiclass models";
203}
204
205/**
206 * The extended Schmidt method needs a customer of every class to tag.
207 *
208 * Schmidt's EXTENSION over plain Schmidt is an alpha correction applied at an
209 * FCFS station, computed from the network with ONE class-r customer TAGGED, that
210 * is at population N - 1_r. A class holding no customer has none to tag: the
211 * sub-problem is formed at a negative population, whose state lattice prod(N+1)
212 * collapses to zero and the recursion indexes an empty array. Plain `schmidt`
213 * forms no such sub-problem, which is why the requirement is the -ext arm's
214 * alone.
215 *
216 * THE TEST IS STATED AT THE FCFS STATION AND NOT AT A CLASS-DEPENDENT ONE,
217 * because the four kernels differ on when they form the correction: MATLAB, this
218 * port and native python form it only where the station's demands differ by
219 * class, the JAR forms it at every FCFS station. Stating the union is what keeps
220 * one rule safe for all four; the case it costs -- an FCFS station whose demands
221 * are identical across classes, one of them empty -- is one where the extension
222 * reduces to plain `schmidt`, which stays offered.
223 *
224 * `njobs` and `fcfs` are the numbers the CALLER'S OWN arm passes: CHAIN-indexed
225 * here as in MATLAB, CLASS-indexed in the JAR and native python, whose arms
226 * aggregate no chains. That difference belongs to those arms, not to this rule.
227 */
228inline std::string mva_schmidt_ext_reason(const std::vector<double>& njobs,
229 const std::vector<bool>& fcfs,
230 const std::string& method) {
231 if (qn::mva_base_method(method) != "schmidt-ext") return "";
232 bool any_fcfs = false;
233 for (std::size_t i = 0; i < fcfs.size(); ++i)
234 if (fcfs[i]) any_fcfs = true;
235 if (!any_fcfs) return "";
236 for (std::size_t r = 0; r < njobs.size(); ++r)
237 if (std::isfinite(njobs[r]) && njobs[r] < 1.0)
238 return "solver_amva: the 'schmidt-ext' method corrects an FCFS station from the "
239 "network with one customer of that class tagged, so it needs every class to "
240 "hold at least one customer; class " +
241 std::to_string(r + 1) +
242 " holds none; use 'schmidt' for the uncorrected recursion";
243 return "";
244}
245
246/**
247 * MVAC is the exact chain recursion over single-server fixed-rate (SSFR) queues
248 * and infinite-server centres of a product-form network, and it recurs on the
249 * queueing centres, so it needs at least one. Neither the server count nor
250 * product form has a registry feature name; the scheduling restriction IS
251 * nameable and lives in `qn::mva_feature_set`.
252 */
253template <class T>
254std::string mva_mvac_reason(const qn::NetworkStruct<T>& L, const std::string& method) {
255 if (qn::mva_base_method(method) != "mvac") return "";
256 if (!L.has_product_form()) return "solver_mvac_analyzer: MVAC requires a product-form model";
257 for (const qn::Station<T>& st : L.stations) {
258 if (st.sched == qn::SchedStrategy::INF || st.sched == qn::SchedStrategy::EXT) continue;
259 // An infinite count is refused here too, as the reference does: a station
260 // scheduled FCFS with infinitely many servers is not an IS centre to MVAC,
261 // and `infSET` below is built from the DISCIPLINE, not the count.
262 if (st.nservers != 1.0)
263 return "solver_mvac_analyzer: MVAC supports single-server (SSFR) queues only; use "
264 "method 'exact' for multiserver stations";
265 }
266 // A pure-delay network (no queueing station) is answered, as solver_mvac.m does: pfqn_mvac
267 // runs on the IS centers alone and the delay rows follow from Little's law.
268 return "";
269}
270
271/**
272 * QNA's station update has an arm for INF, PS and FCFS and none for any other
273 * discipline, so a SIRO, LCFS, LCFS-PR, HOL or priority station used to leave
274 * its whole row of Q, U, R and T at zero and the table was returned as a
275 * solution. The registry expresses this too (`mva_feature_set` drops the
276 * disciplines from QNA's envelope); this is the analyzer's half of it.
277 */
278template <class T>
280 for (std::size_t i = 0; i < L.stations.size(); ++i) {
281 // A Join station carries no service and is skipped by the update below.
282 if (L.stations[i].nodetype == qn::NodeType::Join) continue;
283 const qn::SchedStrategy sched = L.stations[i].sched;
284 if (sched == qn::SchedStrategy::EXT || sched == qn::SchedStrategy::INF ||
285 sched == qn::SchedStrategy::PS || sched == qn::SchedStrategy::FCFS)
286 continue;
287 return std::string("solver_qna: QNA decomposes every station as a GI/G/m centre and has "
288 "no arm for ") +
289 lang::sched_to_text(sched) + " scheduling; use the 'default' or 'lin' methods";
290 }
291 return "";
292}
293
294} // namespace mva
295} // namespace line
296
297#endif // LINE_SOLVERS_MVA_MVA_TYPES_H
A network plus its refreshed NetworkStruct.
bool has_product_form_not_het_fcfs(bool check_means=true) const
Port of sn_has_product_form_not_het_fcfs: LCFS is excluded, and at FCFS the service must be exponenti...
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::vector< NodeDef > nodes
every node, in creation order
bool has_product_form() const
sn_has_product_form, conjunct for conjunct.
Fork-join section metrics: FJQLen, FJRespT and FJTput of every Fork/Join pair.
Dense matrix and non-owning view.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:185
const char * sched_to_text(SchedStrategy s)
Definition lang_types.h:235
std::string mva_single_class_open_reason(const qn::NetworkStruct< T > &L, const std::string &method)
RQNA and RQT decompose an open network into GI/G/1 queues and build one uncertainty set per flow out ...
Definition mva_types.h:187
std::string mva_schmidt_ext_reason(const std::vector< double > &njobs, const std::vector< bool > &fcfs, const std::string &method)
The extended Schmidt method needs a customer of every class to tag.
Definition mva_types.h:228
std::string mva_mvac_reason(const qn::NetworkStruct< T > &L, const std::string &method)
MVAC is the exact chain recursion over single-server fixed-rate (SSFR) queues and infinite-server cen...
Definition mva_types.h:254
std::string mva_qna_scheduling_reason(const qn::NetworkStruct< T > &L)
QNA's station update has an arm for INF, PS and FCFS and none for any other discipline,...
Definition mva_types.h:279
std::string mva_closed_population_reason(const qn::NetworkStruct< T > &L, const std::string &method)
The closed-population AMVA family: an open chain, or a network outside strict product form,...
Definition mva_types.h:154
std::string mva_base_method(const std::string &method)
The method name with any leading "amva." alias stripped.
bool mva_is_closed_population_method(const std::string &method)
The AMVA algorithms whose recursion is over a CLOSED population vector.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
The DECLARED side of the gate: one feature set per solver.
The options SolverMVA reads.
Definition mva_types.h:32
std::string np_priority
options.config.np_priority: which non-preemptive priority correction a HOL station takes.
Definition mva_types.h:45
std::string highvar
Definition mva_types.h:38
std::string multiserver
Definition mva_types.h:37
bool has_init_sol
Warm start, (nstations x nchains) chain-aggregated queue lengths; empty for none.
Definition mva_types.h:54
bool base_has_fork
Whether the model this solve came from has a Fork, which the model handed to the analyzer no longer d...
Definition mva_types.h:66
double rqt_alpha_a
options.config.rqt_alpha_a: arrival tail coefficient in (1,2]; 0 = 2.
Definition mva_types.h:78
std::string rqt_regime
options.config.rqt_regime: which service adaptation regime of the Robust Queueing Theory Table 1 the ...
Definition mva_types.h:73
std::string method
Definition mva_types.h:33
bool rqt_exact
options.config.rqt_exact: use the exact worst case over the uncertainty sets instead of the closed-fo...
Definition mva_types.h:76
double rqt_alpha_s
options.config.rqt_alpha_s: service tail coefficient in (1,2]; 0 = 2.
Definition mva_types.h:80
std::vector< std::vector< double > > interlock
options.config.interlock: the interlocked-flow matrix of Franks (1999), Eq.
Definition mva_types.h:92
std::string fork_join
options.config.fork_join: which fork-join arm the fixed point takes.
Definition mva_types.h:52
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:97
std::vector< T > X
Definition mva_types.h:99
double lG
log of the normalizing constant, the reference's lG.
Definition mva_types.h:119
Matrix< T > AN
Analyzer-supplied station arrival rates (M x K), NaN where the runner derives them from the routing; ...
Definition mva_types.h:127
std::vector< T > C
Definition mva_types.h:99
std::optional< bool > converged
Whether the fixed point met its tolerance, or empty when the handler reports none.
Definition mva_types.h:110
solvers::FjMetrics< T > fj
Fork-join section metrics set by the fork-join fixed point (fj_driver.h); empty otherwise.
Definition mva_types.h:121
A node of the network.
One station of the network.
SchedStrategy sched
double nservers
may be infinite (a Delay, or an inf-scheduled task)
self.result.FJ: one row per Fork/Join pair, one column per class.
Definition fj_metrics.h:43