LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
solver_mam_ldqbd_flatten.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_MAM_SOLVER_MAM_LDQBD_FLATTEN_H
6
#define LINE_SOLVERS_MAM_SOLVER_MAM_LDQBD_FLATTEN_H
7
8
/**
9
* @file
10
* @ingroup line_solvers
11
* The two reductions that let an LD-QBD stand in for an enumerated CTMC.
12
*
13
* Ports of `solver_mam_ldqbd_flatten.m` and `solver_mam_ldqbd_avg.m`. They
14
* exist for one caller, the SolverENV state-vector analyzer, and the shape of
15
* that caller is what explains them: it propagates a DISTRIBUTION over a stage's
16
* state space through the stage's sojourn, so it needs the stage as a flat
17
* generator it can exponentiate, and it needs to reduce an ARBITRARY vector over
18
* that space to means -- not the stationary one, which `solver_mam_ldqbd` alone
19
* would give it.
20
*
21
* WHY FLATTENING IS NOT A LOSS HERE. `solver_mam_ldqbd` produces the chain as
22
* block-tridiagonal (Q0 up, Q1 within, Q2 down) and solves it by a level-by-
23
* level recursion that never forms the whole matrix -- which is the point of a
24
* QBD. The state-vector analyzer cannot use that recursion: it does not want a
25
* stationary vector, it wants `exp(Q t)` applied to a vector it brings with it.
26
* The blocks are finite here (`Nlev` is the closed population, or the open
27
* truncation from `cutoff`), so the flat matrix EXISTS; it is O(Nlev^2 nPhases^2)
28
* to store where the blocks are O(Nlev nPhases^2), and that is the cost of the
29
* question being asked.
30
*
31
* LEVEL 0 IS ONE STATE AND THE REST ARE nPhases WIDE, so the offsets are not a
32
* multiple of the level index. `levelOf` carries the level of each flat state
33
* rather than making the caller recompute it, because getting that mapping wrong
34
* misreports the queue length without making the generator invalid.
35
*/
36
37
#include <cstddef>
38
#include <vector>
39
40
#include "
line/num/number.h
"
41
#include "
line/solvers/mam/solver_mam_ldqbd.h
"
42
#include "
line/util/error.h
"
43
#include "
line/util/matrix.h
"
44
45
namespace
line
{
46
namespace
mam
{
47
48
/** The flat generator of an LD-QBD, with the level each flat state belongs to. */
49
template
<
class
T>
50
struct
LdqbdFlat
{
51
Matrix<T>
Q
;
///< the dense generator over level/phase states
52
std::vector<std::size_t>
levelOf
;
///< levelOf[s] = the queue level of flat state s
53
};
54
55
/** Port of `solver_mam_ldqbd_flatten.m`. */
56
template
<
class
T>
57
LdqbdFlat<T>
solver_mam_ldqbd_flatten
(
const
LdqbdBlocks<T>
& ld) {
58
const
std::size_t Nlev = ld.
Nlev
;
59
if
(ld.
Q1
.size() != Nlev + 1)
60
throw
InputError
(
61
"solver_mam_ldqbd_flatten: the block set carries "
+ std::to_string(ld.
Q1
.size()) +
62
" within-level blocks for "
+ std::to_string(Nlev + 1) +
" levels"
);
63
64
std::vector<std::size_t> levelSize(Nlev + 1, 0), levelStart(Nlev + 2, 0);
65
for
(std::size_t n = 0; n <= Nlev; ++n) {
66
levelSize[n] = ld.
Q1
[n].rows();
67
levelStart[n + 1] = levelStart[n] + levelSize[n];
68
}
69
const
std::size_t dim = levelStart[Nlev + 1];
70
71
LdqbdFlat<T>
out;
72
out.
Q
=
Matrix<T>
(dim, dim,
num_traits<T>::from_int
(0));
73
out.
levelOf
.assign(dim, 0);
74
for
(std::size_t n = 0; n <= Nlev; ++n) {
75
const
std::size_t r0 = levelStart[n], rn = levelSize[n];
76
for
(std::size_t a = 0; a < rn; ++a) out.
levelOf
[r0 + a] = n;
77
for
(std::size_t a = 0; a < rn; ++a)
78
for
(std::size_t b = 0; b < rn; ++b) out.
Q
(r0 + a, r0 + b) = ld.
Q1
[n](a, b);
79
if
(n < Nlev) {
80
const
std::size_t c0 = levelStart[n + 1], cn = levelSize[n + 1];
81
for
(std::size_t a = 0; a < rn; ++a)
82
for
(std::size_t b = 0; b < cn && b < ld.
Q0
[n].cols(); ++b)
83
out.
Q
(r0 + a, c0 + b) = ld.
Q0
[n](a, b);
84
}
85
if
(n >= 1) {
86
// Q2 IS INDEXED BY LEVEL HERE, NOT BY THE MATLAB CELL POSITION.
87
// `solver_mam_ldqbd.m` writes `Q2{n}` for the level n -> n-1 block,
88
// 1-based, so the flatten there reads `Q2{n}`; this port keeps an
89
// unused placeholder at index 0 so that Q0, Q1 and Q2 all line up by
90
// level, which makes the same block `Q2[n]`. Reading `Q2[n-1]`
91
// compiles, keeps the generator valid and conserves the population
92
// -- it just shifts every departure rate down one level, which on a
93
// three-job repairman model moved the mean queue from 1.42105 to
94
// 1.98824 with the total still exactly 3.
95
const
std::size_t c0 = levelStart[n - 1], cn = levelSize[n - 1];
96
for
(std::size_t a = 0; a < rn && a < ld.
Q2
[n].rows(); ++a)
97
for
(std::size_t b = 0; b < cn && b < ld.
Q2
[n].cols(); ++b)
98
out.
Q
(r0 + a, c0 + b) = ld.
Q2
[n](a, b);
99
}
100
}
101
return
out;
102
}
103
104
/** Per-(station,class) means read off an arbitrary distribution over the LD-QBD. */
105
template
<
class
T>
106
struct
LdqbdAvg
{
107
Matrix<T>
QN
,
UN
,
RN
,
TN
;
///< (M x 1), the model being single-class by construction
108
};
109
110
/**
111
* Port of `solver_mam_ldqbd_avg.m`: map a distribution over the flat state
112
* space to means.
113
*
114
* NOT THE STATIONARY DISTRIBUTION. This mirrors the metric formulas of
115
* `solver_mam_ldqbd` but applies them to whatever vector it is handed -- in
116
* practice the TIME-AVERAGE over an environment stage's sojourn, which is not a
117
* stationary law of anything. That is why the formulas are written out again
118
* here rather than shared: the stationary versions in `solver_mam_ldqbd` reach
119
* for quantities (the level recursion's own R matrices) that only exist at the
120
* fixed point.
121
*
122
* The vector is clipped at zero and renormalized first, as the reference does:
123
* a transient vector that a quadrature has pushed a hair negative is a numerical
124
* artifact of the propagation, not a signed measure to be propagated further.
125
*/
126
template
<
class
T>
127
LdqbdAvg<T>
solver_mam_ldqbd_avg
(
const
LdqbdBlocks<T>
& ld,
const
std::vector<T>& piflat_in,
128
const
std::vector<std::size_t>& levelOf) {
129
const
T zero =
num_traits<T>::from_int
(0), one =
num_traits<T>::from_int
(1);
130
const
std::size_t Nlev = ld.
Nlev
, M = ld.
M
;
131
if
(ld.
queueIdx
== 0 || ld.
refIdx
== 0 || M == 0)
132
throw
InputError
(
"solver_mam_ldqbd_avg: the block set names no queue or reference station"
);
133
if
(piflat_in.size() != levelOf.size())
134
throw
InputError
(
135
"solver_mam_ldqbd_avg: the distribution and the level map disagree on the size of the "
136
"state space"
);
137
138
std::vector<T> piflat = piflat_in;
139
T total = zero;
140
for
(std::size_t s = 0; s < piflat.size(); ++s) {
141
if
(
num_traits<T>::to_double
(piflat[s]) < 0.0) piflat[s] = zero;
142
total = T(total + piflat[s]);
143
}
144
if
(
num_traits<T>::to_double
(total) > 0.0)
145
for
(std::size_t s = 0; s < piflat.size(); ++s) piflat[s] = T(piflat[s] / total);
146
147
std::vector<T> pLevel(Nlev + 1, zero);
148
for
(std::size_t s = 0; s < piflat.size(); ++s)
149
if
(levelOf[s] <= Nlev) pLevel[levelOf[s]] = T(pLevel[levelOf[s]] + piflat[s]);
150
151
T mean_queue = zero;
152
for
(std::size_t n = 0; n <= Nlev; ++n)
153
mean_queue = T(mean_queue + T(
num_traits<T>::from_int
(
static_cast<
long
long
>
(n)) * pLevel[n]));
154
155
// Utilization is the fraction of the station's PEAK capacity in use,
156
// sum_n p(n)*sf(n)/utilPeak, the work-based convention CTMC, MVA, NC and
157
// serial SSA all report. Without load dependence sf(n) = min(n,c) and
158
// utilPeak = c, giving the mean fraction of the c servers in use; at c = 1
159
// that is sf(n) = 1 for every n >= 1, so the sum collapses to 1 - p(0).
160
// The `/utilPeak` is inside the sum, so the branches below must NOT divide
161
// again.
162
T
util
= zero;
163
{
164
const
T peak =
num_traits<T>::from_double
(ld.
utilPeak
);
165
for
(std::size_t n = 1; n <= Nlev && n < ld.
sf
.size(); ++n)
166
util
= T(
util
+ T(ld.
sf
[n] / peak * pLevel[n]));
167
}
168
169
LdqbdAvg<T>
out;
170
out.
QN
=
Matrix<T>
(M, 1, zero);
171
out.
UN
=
Matrix<T>
(M, 1, zero);
172
out.
RN
=
Matrix<T>
(M, 1, zero);
173
out.
TN
=
Matrix<T>
(M, 1, zero);
174
const
std::size_t qi = ld.
queueIdx
- 1, ri = ld.
refIdx
- 1;
175
176
if
(ld.
isOpen
) {
177
// The throughput is the arrival rate less the share LOST at the
178
// truncation level, which is what makes the open answer depend on
179
// `cutoff` rather than silently ignoring the loss.
180
const
T X = T(ld.
lambda_eff
* T(one - pLevel[Nlev]));
181
const
T Rq =
num_traits<T>::to_double
(X) > 0.0 ? T(mean_queue / X) : zero;
182
out.
TN
(ri, 0) = X;
183
out.
QN
(qi, 0) = mean_queue;
184
out.
UN
(qi, 0) =
util
;
185
out.
RN
(qi, 0) = Rq;
186
out.
TN
(qi, 0) = X;
187
}
else
{
188
const
T mean_delay = T(
num_traits<T>::from_double
(ld.
N
) - mean_queue);
189
const
T X = T(mean_delay * ld.
lambda_eff
);
190
const
T Rq =
num_traits<T>::to_double
(X) > 0.0 ? T(mean_queue / X) : zero;
191
out.
QN
(ri, 0) = mean_delay;
192
// A Delay's utilization IS its mean population: it has one server per
193
// job, so "fraction busy" has no other meaning there.
194
out.
UN
(ri, 0) = mean_delay;
195
out.
RN
(ri, 0) =
num_traits<T>::to_double
(ld.
delayRate
) > 0.0 ? T(one / ld.
delayRate
) : zero;
196
out.
TN
(ri, 0) = X;
197
out.
QN
(qi, 0) = mean_queue;
198
out.
UN
(qi, 0) =
util
;
199
out.
RN
(qi, 0) = Rq;
200
out.
TN
(qi, 0) = X;
201
}
202
return
out;
203
}
204
205
}
// namespace mam
206
}
// namespace line
207
208
#endif
// LINE_SOLVERS_MAM_SOLVER_MAM_LDQBD_FLATTEN_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::Matrix
Definition
matrix.h:56
line::Matrix::Matrix
Matrix()
Definition
matrix.h:58
error.h
The exception types the port throws.
matrix.h
Dense matrix and non-owning view.
line::mam
Definition
amap2_adjust_gamma.h:78
line::mam::solver_mam_ldqbd_flatten
LdqbdFlat< T > solver_mam_ldqbd_flatten(const LdqbdBlocks< T > &ld)
Port of solver_mam_ldqbd_flatten.m.
Definition
solver_mam_ldqbd_flatten.h:57
line::mam::solver_mam_ldqbd_avg
LdqbdAvg< T > solver_mam_ldqbd_avg(const LdqbdBlocks< T > &ld, const std::vector< T > &piflat_in, const std::vector< std::size_t > &levelOf)
Port of solver_mam_ldqbd_avg.m: map a distribution over the flat state space to means.
Definition
solver_mam_ldqbd_flatten.h:127
line::util
Definition
line_console.h:45
line
Definition
aoi_dist2ph.h:52
number.h
Number-type abstraction for the templated API port.
solver_mam_ldqbd.h
Port of solver_mam_ldqbd.m: the level-dependent QBD analyzer for a single-class network of one infini...
line::mam::LdqbdAvg
Per-(station,class) means read off an arbitrary distribution over the LD-QBD.
Definition
solver_mam_ldqbd_flatten.h:106
line::mam::LdqbdAvg::TN
Matrix< T > TN
(M x 1), the model being single-class by construction
Definition
solver_mam_ldqbd_flatten.h:107
line::mam::LdqbdAvg::UN
Matrix< T > UN
Definition
solver_mam_ldqbd_flatten.h:107
line::mam::LdqbdAvg::RN
Matrix< T > RN
Definition
solver_mam_ldqbd_flatten.h:107
line::mam::LdqbdAvg::QN
Matrix< T > QN
Definition
solver_mam_ldqbd_flatten.h:107
line::mam::LdqbdBlocks
The LD-QBD blocks and parameters, the reference's optional eighth output.
Definition
solver_mam_ldqbd.h:70
line::mam::LdqbdBlocks::isOpen
bool isOpen
Definition
solver_mam_ldqbd.h:75
line::mam::LdqbdBlocks::Nlev
std::size_t Nlev
Definition
solver_mam_ldqbd.h:72
line::mam::LdqbdBlocks::Q0
std::vector< Matrix< T > > Q0
Definition
solver_mam_ldqbd.h:71
line::mam::LdqbdBlocks::Q1
std::vector< Matrix< T > > Q1
Definition
solver_mam_ldqbd.h:71
line::mam::LdqbdBlocks::Q2
std::vector< Matrix< T > > Q2
Definition
solver_mam_ldqbd.h:71
line::mam::LdqbdBlocks::lambda_eff
T lambda_eff
Definition
solver_mam_ldqbd.h:89
line::mam::LdqbdBlocks::sf
std::vector< T > sf
Per-level service factor sf(n), with sf[0] unused so it lines up by level.
Definition
solver_mam_ldqbd.h:81
line::mam::LdqbdBlocks::queueIdx
std::size_t queueIdx
Definition
solver_mam_ldqbd.h:76
line::mam::LdqbdBlocks::utilPeak
double utilPeak
The capacity that normalizes the utilization: max(c, max(alpha)), the LARGEST factor the load-depende...
Definition
solver_mam_ldqbd.h:88
line::mam::LdqbdBlocks::N
double N
Definition
solver_mam_ldqbd.h:91
line::mam::LdqbdBlocks::delayRate
T delayRate
Definition
solver_mam_ldqbd.h:90
line::mam::LdqbdBlocks::refIdx
std::size_t refIdx
Definition
solver_mam_ldqbd.h:76
line::mam::LdqbdBlocks::M
std::size_t M
Definition
solver_mam_ldqbd.h:76
line::mam::LdqbdFlat
The flat generator of an LD-QBD, with the level each flat state belongs to.
Definition
solver_mam_ldqbd_flatten.h:50
line::mam::LdqbdFlat::Q
Matrix< T > Q
the dense generator over level/phase states
Definition
solver_mam_ldqbd_flatten.h:51
line::mam::LdqbdFlat::levelOf
std::vector< std::size_t > levelOf
levelOf[s] = the queue level of flat state s
Definition
solver_mam_ldqbd_flatten.h:52
line::num_traits
Definition
number.h:111
include
line
solvers
mam
solver_mam_ldqbd_flatten.h
Generated by
1.18.0