LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
lqn_boxbounds.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_API_LQN_LQN_BOXBOUNDS_H
6#define LINE_API_LQN_LQN_BOXBOUNDS_H
7
8/**
9 * @file
10 * @ingroup api_lqn
11 * Majumdar-Woodside robust box bounds on the throughput of a layered network.
12 *
13 * Port of `matlab/src/api/lqn/lqn_boxbounds.m`. The layered model is collapsed
14 * onto its PROCESSOR-CONTENTION model -- the stations are the hosts and the
15 * classes are the reference-task call chains -- and `pfqn_mwrbb` is evaluated
16 * on it. The per-chain demand at a processor is the total host demand executed
17 * there during one cycle of the reference task, obtained by walking the
18 * entry/activity/call graph and scaling by the mean number of synchronous
19 * calls.
20 *
21 * WHAT THIS IS AND IS NOT. It generalizes the classical LQN Type-1 throughput
22 * bound X <= mult/(Z + D_total) -- the no-contention bound `lqns -b` reports --
23 * by adding the processor-utilization upper bound and the Majumdar-Woodside
24 * lower bound. It does NOT model a software bottleneck (a task with finitely
25 * many threads is not a station here) and it assumes activities execute
26 * sequentially, so an OR-branch probability and a loop count are ignored rather
27 * than averaged. A bound that ignores a resource is still a valid bound on the
28 * model it does describe, which is why this is worth having; it is not a
29 * substitute for solving the layers.
30 *
31 * NO ITERATION HAPPENS HERE. This is what `method = 'mwba.upper'` /
32 * `'mwba.lower'` reports INSTEAD of a fixed point, so a caller asking for it
33 * pays one graph walk and one bound evaluation, and gets NaN for every metric
34 * the bound does not define (queue lengths, response times, residence times).
35 */
36
37#include <cstddef>
38#include <vector>
39
43#include "line/num/number.h"
44#include "line/util/error.h"
45#include "line/util/matrix.h"
46
47namespace line {
48namespace lqn {
49
50/** What lqn_boxbounds returns, in the layout of the reference's `out` struct. */
51template <class T>
53 std::vector<std::size_t> refidx; ///< (R) absolute indices of the reference tasks
54 std::vector<T> Xlo, Xup; ///< (R) per-chain throughput bounds
55 /// (nidx+1) bounds propagated to every element; `defined` marks the entries
56 /// the reference leaves as NaN, i.e. an element no reference chain visits.
57 std::vector<T> TN_lo, TN_up, UN_lo, UN_up;
58 std::vector<bool> defined_T, defined_U;
59 Matrix<T> D; ///< (nhosts x R) per-chain demand at each processor
60};
61
62namespace detail {
63
64/** SchedStrategy -> the Majumdar-Woodside discipline code, discCode of the reference. */
65inline pfqn::MwrbbSched lqn_disc_code(lang::SchedStrategy s) {
67 switch (s) {
68 case SchedStrategy::FCFS:
70 case SchedStrategy::PS:
71 case SchedStrategy::DPS:
72 case SchedStrategy::GPS:
73 case SchedStrategy::PSPRIO:
74 case SchedStrategy::DPSPRIO:
75 case SchedStrategy::GPSPRIO:
77 case SchedStrategy::HOL:
79 case SchedStrategy::FCFSPRPRIO:
80 case SchedStrategy::LCFSPRPRIO:
82 default:
83 // ABA full contention: discipline-independent, hence the safe answer
84 // for a discipline the bound has no shape for.
86 }
87}
88
89template <class T>
90void lqn_box_visit_entry(const LqnStruct<T>& lqn, std::size_t eidx, const T& mult, std::size_t nH,
91 std::vector<T>& d, std::vector<T>& vis);
92
93/** One activity: charge its host demand, then follow its synchronous calls. */
94template <class T>
95void lqn_box_visit_activity(const LqnStruct<T>& lqn, std::size_t aidx, const T& mult,
96 std::size_t nH, std::vector<T>& d, std::vector<T>& vis) {
97 vis[aidx] = T(vis[aidx] + mult);
98 const std::size_t tidx = lqn.parent[aidx];
99 const std::size_t hidx = lqn.parent[tidx]; // hosts are their own shift, hshift = 0
100 const T hd = lqn.hostdem[aidx].disabled ? num_traits<T>::from_int(0) : lqn.hostdem[aidx].mean;
101 if (hidx >= 1 && hidx <= nH) d[hidx] = T(d[hidx] + mult * hd);
102 for (std::size_t cidx : lqn.callsof[aidx])
103 if (lqn.calltype[cidx] == lang::CallType::SYNC)
104 lqn_box_visit_entry(lqn, lqn.callpair_dst[cidx],
105 T(mult * lqn.callproc_mean[cidx]), nH, d, vis);
106}
107
108/** One entry: itself, its task, and every activity of its own task. */
109template <class T>
110void lqn_box_visit_entry(const LqnStruct<T>& lqn, std::size_t eidx, const T& mult, std::size_t nH,
111 std::vector<T>& d, std::vector<T>& vis) {
112 vis[eidx] = T(vis[eidx] + mult);
113 const std::size_t tidx = lqn.parent[eidx];
114 if (tidx >= 1 && tidx < vis.size()) vis[tidx] = T(vis[tidx] + mult);
115 for (std::size_t aidx : lqn.actsof[eidx])
116 if (lqn.parent[aidx] == lqn.parent[eidx])
117 lqn_box_visit_activity(lqn, aidx, mult, nH, d, vis);
118}
119
120} // namespace detail
121
122/**
123 * Evaluate the box bounds of `lqn`.
124 *
125 * A reference task of infinite multiplicity is treated as one customer, as the
126 * reference does: the bound is over a closed chain and an infinite population
127 * has no lower bound to report.
128 */
129template <class T>
131 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
132 const std::size_t nidx = lqn.nidx, nH = lqn.nhosts;
133
134 LqnBoxBounds<T> out;
135 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
136 const std::size_t tidx = lqn.tshift + t;
137 if (lqn.isref[tidx]) out.refidx.push_back(tidx);
138 }
139 const std::size_t R = out.refidx.size();
140 if (R == 0)
141 throw InputError("lqn_boxbounds: the model declares no reference task, so it has no chain");
142
143 Matrix<T> D(nH + 1, R, zero); // row 0 unused, matching the 1-based host index
144 Matrix<T> Vis(nidx + 1, R, zero);
145 std::vector<T> Nref(R, one), Zref(R, zero);
146 for (std::size_t r = 0; r < R; ++r) {
147 const std::size_t tidx = out.refidx[r];
148 const double mult = lqn.mult[tidx];
149 Nref[r] = std::isfinite(mult) ? num_traits<T>::from_double(mult) : one;
150 Zref[r] = lqn.think[tidx].disabled ? zero : lqn.think[tidx].mean;
151 std::vector<T> d(nH + 1, zero), vis(nidx + 1, zero);
152 for (std::size_t eidx : lqn.entriesof[tidx])
153 detail::lqn_box_visit_entry(lqn, eidx, one, nH, d, vis);
154 for (std::size_t h = 0; h <= nH; ++h) D(h, r) = d[h];
155 for (std::size_t i = 0; i <= nidx; ++i) Vis(i, r) = vis[i];
156 }
157
158 // The bound's own station table: one row per host, visits as an indicator,
159 // service the whole per-cycle demand. That is the reference's V = D > 0,
160 // S = D, i.e. the demand is charged once per cycle rather than per visit.
161 Matrix<T> V(nH, R, zero), S(nH, R, zero);
162 for (std::size_t h = 1; h <= nH; ++h)
163 for (std::size_t r = 0; r < R; ++r) {
164 S(h - 1, r) = D(h, r);
165 V(h - 1, r) = D(h, r) > zero ? one : zero;
166 }
167 std::vector<pfqn::MwrbbSched> sched(nH);
168 for (std::size_t h = 1; h <= nH; ++h) sched[h - 1] = detail::lqn_disc_code(lqn.sched[h]);
169 const std::vector<int> prio(R, 0); // equal class priority, as the reference sets
170
171 const pfqn::MwrbbBounds<T> b = pfqn::pfqn_mwrbb(V, S, Nref, Zref, sched, prio);
172 out.Xlo = b.Xlo;
173 out.Xup = b.Xup;
174
175 out.TN_lo.assign(nidx + 1, zero);
176 out.TN_up.assign(nidx + 1, zero);
177 out.UN_lo.assign(nidx + 1, zero);
178 out.UN_up.assign(nidx + 1, zero);
179 out.defined_T.assign(nidx + 1, false);
180 out.defined_U.assign(nidx + 1, false);
181 for (std::size_t i = 1; i <= nidx; ++i) {
182 bool visited = false;
183 for (std::size_t r = 0; r < R; ++r)
184 if (Vis(i, r) > zero) visited = true;
185 if (!visited) continue;
186 out.defined_T[i] = true;
187 for (std::size_t r = 0; r < R; ++r) {
188 out.TN_lo[i] = T(out.TN_lo[i] + b.Xlo[r] * Vis(i, r));
189 out.TN_up[i] = T(out.TN_up[i] + b.Xup[r] * Vis(i, r));
190 }
191 }
192 for (std::size_t h = 1; h <= nH; ++h) {
193 out.defined_U[h] = true;
194 for (std::size_t r = 0; r < R; ++r) {
195 out.UN_lo[h] = T(out.UN_lo[h] + b.Xlo[r] * D(h, r));
196 out.UN_up[h] = T(out.UN_up[h] + b.Xup[r] * D(h, r));
197 }
198 }
199 out.D = Matrix<T>(nH, R, zero);
200 for (std::size_t h = 1; h <= nH; ++h)
201 for (std::size_t r = 0; r < R; ++r) out.D(h - 1, r) = D(h, r);
202 return out;
203}
204
205} // namespace lqn
206} // namespace line
207
208#endif // LINE_API_LQN_LQN_BOXBOUNDS_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
LayeredNetworkStruct, the flattened description of a layered queueing network.
Dense matrix and non-owning view.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
LqnBoxBounds< T > lqn_boxbounds(const LqnStruct< T > &lqn)
Evaluate the box bounds of lqn.
MwrbbSched
Station discipline codes, matching the MATLAB sched argument.
Definition pfqn_mwrbb.h:48
MwrbbBounds< T > pfqn_mwrbb(const Matrix< T > &V, const Matrix< T > &S, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< MwrbbSched > &sched, const std::vector< int > &prio)
Majumdar-Woodside robust box bounds on the per-class throughput of a closed multiclass network with m...
Definition pfqn_mwrbb.h:161
Number-type abstraction for the templated API port.
Majumdar-Woodside robust box bounds on the per-class throughput of a closed multiclass network with m...
What lqn_boxbounds returns, in the layout of the reference's out struct.
std::vector< T > TN_up
std::vector< T > Xup
(R) per-chain throughput bounds
std::vector< bool > defined_T
std::vector< bool > defined_U
std::vector< std::size_t > refidx
(R) absolute indices of the reference tasks
std::vector< T > UN_lo
std::vector< T > Xlo
std::vector< T > TN_lo
(nidx+1) bounds propagated to every element; defined marks the entries the reference leaves as NaN,...
std::vector< T > UN_up
Matrix< T > D
(nhosts x R) per-chain demand at each processor
Return value of pfqn_mwrbb, mirroring [Xlo, Xup, Wlo].
Definition pfqn_mwrbb.h:52
std::vector< T > Xlo
Definition pfqn_mwrbb.h:53
std::vector< T > Xup
Definition pfqn_mwrbb.h:54