LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mvac.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_SOLVER_MVAC_H
6#define LINE_SOLVERS_MVA_SOLVER_MVAC_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * MVAC, exact mean value analysis BY CHAIN (Conway, de Souza e Silva and
12 * Lavenberg, IEEE Trans. Computers 38(3):432-442, 1989). Port of
13 * `solver_mvac.m`.
14 *
15 * MVAC IS EXACT for its model class, not an approximation: it returns what the
16 * classic MVA recursion returns, by a different route. Where `pfqn_mva` recurs
17 * on the population vector at a cost of prod(N+1), MVAC recurs on the CHAINS,
18 * replacing each removed chain by self-looping single-customer chains, so its
19 * cost is governed by the number of DISTINCT demand columns. That is the whole
20 * reason to offer it beside `exact`, and it is why any disagreement with exact
21 * MVA on a model both accept is a defect rather than a tolerance.
22 *
23 * ITS MODEL CLASS IS NARROWER THAN THE `exact` PATH's, and every restriction is
24 * refused by name rather than approximated:
25 *
26 * closed only an open chain has no population to recur on
27 * product form only the recursion is the BCMP one
28 * single server only the api implements the SSFR arrival theorem, and has
29 * no multiserver correction; `pfqn_mvacld` is the
30 * load-dependent sibling, and the reference does NOT
31 * route here to it -- it refuses, so that a multiserver
32 * model takes the `exact` path it is already solved by
33 *
34 * TWO CHAIN-LEVEL QUANTITIES ARE COMPUTED AND THEN DISCARDED by the reference,
35 * which passes an empty Qchain and Uchain to the deaggregation. The class-level
36 * queue length is therefore reconstructed from the residence time by Little's
37 * law and the utilization from the utilization law, NOT split from the chain
38 * matrices by the visit share. Handing the chain matrices over instead would
39 * change the numbers on any chain holding more than one class, which is why the
40 * reference's utilization renormalization (its `sum(Uchain) > 1` rescaling) is
41 * not reproduced here: it cannot reach the answer, and porting dead arithmetic
42 * would suggest it does. The cycle time is discarded the same way, and survives
43 * only as the divisor that turns the population into the throughput.
44 *
45 * Arithmetic: EXACT-CAPABLE, and therefore ungated. `pfqn_mvac` forms no
46 * normalizing constant and evaluates no logarithm -- additions, multiplications
47 * and divisions in the field of the inputs only -- so this analyzer runs under
48 * exact arithmetic and returns exact fractions there. Contrast `solver_sqd`,
49 * whose calibration is transcendental and is gated for that reason. `lG` is
50 * NaN: MVAC forms no normalizing constant, and the reference says so rather
51 * than reporting a zero that reads as one.
52 */
53
54#include <cmath>
55#include <cstddef>
56#include <limits>
57#include <string>
58#include <vector>
59
64#include "line/util/error.h"
65
66namespace line {
67namespace mva {
68
69/** Port of `solver_mvac.m`. */
70template <class T>
73 // `options.tol` is read by the reference only inside the utilization
74 // renormalization the deaggregation discards; nothing else here is tuned.
75 (void)opt;
76 const T zero = num_traits<T>::from_int(0);
78 const std::size_t M = L.nstations, K = L.nchains;
79
80 // One predicate for the gate and the run: list_valid_methods asks
81 // mva_mvac_reason before it advertises 'mvac', so a listed row is a row that
82 // runs and the refusal reads the same either way.
83 {
84 const std::string mvac_reason = mva_mvac_reason(L, "mvac");
85 if (!mvac_reason.empty()) throw UnsupportedError(mvac_reason);
86 }
87 for (std::size_t c = 0; c < K; ++c) {
88 if (!std::isfinite(d.Nchain[c]))
89 throw UnsupportedError(
90 "solver_mvac_analyzer: MVAC supports closed models only; use method 'exact' for "
91 "open or mixed networks");
92 if (d.Nchain[c] != std::floor(d.Nchain[c]))
93 throw UnsupportedError(
94 "solver_mvac_analyzer: the MVAC recursion removes customers one at a time and has "
95 "no fractional-population form");
96 }
97
98 std::vector<std::size_t> infSET, qSET; // 0-based station indices
99 for (std::size_t i = 0; i < M; ++i) {
100 switch (L.stations[i].sched) {
101 case SchedStrategy::EXT: break; // no external world in a closed model
102 case SchedStrategy::INF: infSET.push_back(i); break;
103 case SchedStrategy::PS:
104 case SchedStrategy::LCFSPR:
105 case SchedStrategy::FCFS:
106 case SchedStrategy::SIRO: qSET.push_back(i); break;
107 default:
108 throw UnsupportedError(std::string("solver_mvac_analyzer: MVAC does not support ") +
109 lang::sched_to_text(L.stations[i].sched) + " scheduling");
110 }
111 }
112
113 // A chain with no jobs carries no work. The reference solves on the active
114 // set and re-expands, because an empty column would make the api recur on a
115 // chain that has no customer to remove.
116 std::vector<std::size_t> rset;
117 for (std::size_t c = 0; c < K; ++c)
118 if (d.Nchain[c] != 0.0) rset.push_back(c);
119
120 std::vector<T> Xchain(K, zero);
121 Matrix<T> Qchain(M, K, zero), Tchain(M, K, zero), Wchain(M, K, zero), Rchain(M, K, zero);
122 std::vector<T> Ccycle(K, zero);
123
124 if (!rset.empty()) {
125 const std::size_t Mq = qSET.size(), Rr = rset.size();
126 Matrix<T> Lq(Mq, Rr, zero), Zq(1, Rr, zero);
127 std::vector<int> N(Rr, 0);
128 for (std::size_t j = 0; j < Rr; ++j) {
129 const std::size_t c = rset[j];
130 N[j] = static_cast<int>(std::llround(d.Nchain[c]));
131 for (std::size_t a = 0; a < Mq; ++a)
132 Lq(a, j) = T(d.STchain(qSET[a], c) * d.Vchain(qSET[a], c));
133 // every delay folds into one think time per chain
134 for (std::size_t i : infSET) Zq(0, j) += T(d.STchain(i, c) * d.Vchain(i, c));
135 }
136
137 const pfqn::MvacResult<T> mr = pfqn::pfqn_mvac(Lq, N, Zq);
138 for (std::size_t j = 0; j < Rr; ++j) {
139 Xchain[rset[j]] = mr.X[j];
140 for (std::size_t a = 0; a < Mq; ++a) Qchain(qSET[a], rset[j]) = mr.Q(a, j);
141 }
142 for (std::size_t i : infSET)
143 for (std::size_t c = 0; c < K; ++c)
144 Qchain(i, c) = T(Xchain[c] * d.STchain(i, c) * d.Vchain(i, c));
145
146 for (std::size_t c : rset) {
147 for (std::size_t i : infSET) Wchain(i, c) = d.STchain(i, c);
148 // The isinf(nservers) branch the reference guards this with is
149 // unreachable: the gate above already refused any queueing station
150 // whose server count is not exactly one.
151 for (std::size_t i : qSET) {
152 if (d.Vchain(i, c) == zero || Xchain[c] == zero) continue;
153 Wchain(i, c) = T(Qchain(i, c) / (Xchain[c] * d.Vchain(i, c)));
154 }
155 }
156
157 // The throughput is re-derived from the residence times rather than
158 // taken from the api, which is what makes the reported Q, T and X
159 // consistent with one another at the model level.
160 for (std::size_t c : rset) {
161 T wsum = zero;
162 for (std::size_t i = 0; i < M; ++i) wsum += Wchain(i, c);
163 if (wsum == zero) {
164 Xchain[c] = zero;
165 } else {
166 for (std::size_t i = 0; i < M; ++i)
167 Ccycle[c] += T(d.Vchain(i, c) * Wchain(i, c));
168 Xchain[c] = T(num_traits<T>::from_double(d.Nchain[c]) / Ccycle[c]);
169 }
170 for (std::size_t i = 0; i < M; ++i) {
171 Qchain(i, c) = T(Xchain[c] * d.Vchain(i, c) * Wchain(i, c));
172 Tchain(i, c) = T(Xchain[c] * d.Vchain(i, c));
173 }
174 }
175 }
176
177 for (std::size_t i = 0; i < M; ++i)
178 for (std::size_t c = 0; c < K; ++c)
179 if (Tchain(i, c) > zero) Rchain(i, c) = T(Qchain(i, c) / Tchain(i, c));
180
181 const ClassResults<T> cr =
182 sn_deaggregate_chain_results(L, d, Matrix<T>(), Matrix<T>(), Rchain, Tchain, Xchain);
183 MvaSolution<T> out;
184 out.Q = cr.Q;
185 out.U = cr.U;
186 out.R = cr.R;
187 out.Tp = cr.Tp;
188 out.C = cr.C;
189 out.X = cr.X;
190 out.method = "mvac";
191 out.iter = 0;
192 out.lG = std::numeric_limits<double>::quiet_NaN();
193 return out;
194}
195
196} // namespace mva
197} // namespace line
198
199#endif // LINE_SOLVERS_MVA_SOLVER_MVAC_H
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::vector< Station< T > > stations
stations[k-1] is the k-th station
The exception types the port throws.
The option and result types every MVA analyzer shares.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
const char * sched_to_text(SchedStrategy s)
Definition lang_types.h:230
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:245
MvaSolution< T > solver_mvac_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mvac.m.
Definition solver_mvac.h:71
ClassResults< T > sn_deaggregate_chain_results(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const Matrix< T > &Qchain, const Matrix< T > &Uchain, const Matrix< T > &Rchain, const Matrix< T > &Tchain, const std::vector< T > &Xchain)
Port of sn_deaggregate_chain_results.
Definition sn_chain.h:214
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
Definition sn_chain.h:63
MvacResult< T > pfqn_mvac(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z)
MVAC: exact mean value analysis BY CHAIN of a closed multichain product-form network (Conway,...
Definition pfqn_mvac.h:246
A queueing network and its refreshed NetworkStruct.
MVAC: exact mean value analysis BY CHAIN of a closed multichain product-form network (Conway,...
Chain aggregation and de-aggregation.
The chain-level view of a layer, as sn_get_demands_chain returns it.
Definition sn_chain.h:46
std::vector< double > Nchain
(C) population, infinite for an open chain
Definition sn_chain.h:51
Matrix< T > STchain
(M x C) mean service time
Definition sn_chain.h:48
Matrix< T > Vchain
(M x C) visits
Definition sn_chain.h:49
Class-level results, as sn_deaggregate_chain_results returns them.
Definition sn_chain.h:191
std::vector< T > C
Definition sn_chain.h:193
std::vector< T > X
Definition sn_chain.h:193
The options SolverMVA reads.
Definition mva_types.h:31
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:96
std::vector< T > X
Definition mva_types.h:98
double lG
log of the normalizing constant, the reference's lG.
Definition mva_types.h:118
std::vector< T > C
Definition mva_types.h:98
Return value of pfqn_mvac, mirroring [XN, QN, UN, CN].
Definition pfqn_mvac.h:75
std::vector< T > X
(R) per-class throughput
Definition pfqn_mvac.h:76
Matrix< T > Q
(M x R) per-class queue length at the SSFR queues
Definition pfqn_mvac.h:77