LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
lsn_max_multiplicity.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_LSN_MAX_MULTIPLICITY_H
6#define LINE_API_LQN_LSN_MAX_MULTIPLICITY_H
7
8/**
9 * @file
10 * @ingroup api_lqn
11 * Maximum sustainable multiplicity (concurrency level) of every element of a
12 * layered software network.
13 *
14 * Templated port of matlab/src/api/lsn/lsn_max_multiplicity.m, cross-checked
15 * against jar/src/main/java/jline/api/lsn/LsnMaxMultiplicity.java. It lives
16 * under api/lqn/ because the port adds no api/lsn/ directory of its own; the
17 * namespace is line::lsn, matching the MATLAB domain.
18 *
19 * Concurrency is propagated along the call graph in topological order (Kahn):
20 * a reference task seeds its own multiplicity, an entry with open arrivals
21 * seeds one thread, and every element passes on min(what reaches it, what it
22 * can hold). A setup task is exempt from the caller bound: its instances
23 * are provisioned by the platform rather than spawned by its callers, so it
24 * passes on its declared multiplicity. A non-reference task with infinite
25 * multiplicity ends up unbounded.
26 *
27 * REPLICATION (BUG-111). What flows is threads totalled over the replicas: a
28 * reference task seeds mult*r, an open-arrival entry one thread per replica of
29 * its task, and an element passes on min(inflow, mult*r). The value returned is
30 * what ONE replica can hold busy, min(inflow, mult), the divisor
31 * lsn_host_reach_mult takes. A host replica receives only the task replicas it
32 * runs (r(task)/r(host) of them), each holding at most its own reach. With no
33 * replication every r is 1 and this is the unreplicated algorithm unchanged.
34 *
35 * SCOPE: the port takes the six plain-data fields the algorithm actually
36 * reads -- the call graph, the multiplicities, the element types, the
37 * reference and setup-task flags, and the per-entry open-arrival flag --
38 * rather than a LayeredNetworkStruct, so no model layer is needed.
39 *
40 * DIVERGENCE, MATLAB vs JAR: MATLAB also seeds inflow(i) = 1 for an ENTRY that
41 * has an open arrival (lsn.arrival{i} non-empty); the JAR omits that branch
42 * entirely, so an open-arrival entry reachable from no reference task gets
43 * outflow 0 there and 1 in MATLAB. This port follows MATLAB, the reference
44 * implementation, and exposes the flag as entry_has_arrival.
45 *
46 * ARITHMETIC: comparisons and additions only, so a finite field computation,
47 * exact in the exact instantiation. Infinite multiplicity is carried by an
48 * explicit flag instead of a floating infinity, both because Rational has no
49 * infinity and because Inf + Inf and min(Inf, Inf) are then decided by the
50 * algorithm rather than by the number type.
51 */
52
53#include <algorithm>
54#include <cstddef>
55#include <deque>
56#include <vector>
57
58#include "line/num/number.h"
59#include "line/util/error.h"
60#include "line/util/matrix.h"
61
62namespace line {
63namespace lsn {
64
65/** Element kinds, with the values of MATLAB's LayeredNetworkElement. */
66enum class LsnElementType { HOST = 0, TASK = 1, ENTRY = 2, ACTIVITY = 3, CALL = 4 };
67
68/** A multiplicity, possibly infinite; MATLAB's mult(i) = Inf. */
69template <class T>
71 bool infinite = false;
73
74 static Multiplicity finite(const T& v) {
76 r.infinite = false;
77 r.value = v;
78 return r;
79 }
80 static Multiplicity of(long v) { return finite(num_traits<T>::from_int(v)); }
81 static Multiplicity inf() {
83 r.infinite = true;
84 return r;
85 }
86
87 bool positive() const { return infinite || value > num_traits<T>::from_int(0); }
88};
89
90/** a + b, with infinity absorbing. */
91template <class T>
96
97/** min(a,b), with infinity as the top element. */
98template <class T>
100 if (a.infinite) return b;
101 if (b.infinite) return a;
102 return a.value < b.value ? a : b;
103}
104
105/** a times the integer k, with infinity absorbing. */
106template <class T>
108 if (a.infinite) return a;
110}
111
112/** The plain-data fields of a layered software network read by the algorithm. */
113template <class T>
114struct LsnInput {
115 Matrix<T> dag; ///< (n x n) call graph; an edge is a strictly positive entry
116 std::vector<Multiplicity<T>> mult; ///< (n) declared multiplicity; short vectors are padded with Inf
117 std::vector<LsnElementType> type; ///< (n) element kind
118 std::vector<bool> isref; ///< (n) reference task flag
119 std::vector<bool> hassetup; ///< (n) setup task flag; may be empty
120 std::vector<bool> entry_has_arrival; ///< (n) entry with an open arrival; may be empty
121 std::vector<long> repl; ///< (n) replication of each element (entries and activities: their task's); may be empty
122};
123
124namespace detail {
125
126/**
127 * Kahn topological sort of an adjacency matrix, port of matlab/util/kahn.m.
128 * The queue is FIFO, as in both MATLAB and the JAR, so the order is identical.
129 * A cycle leaves the order short, which the caller must reject: MATLAB then
130 * indexes with a zero and the JAR runs off the end of its list, so neither
131 * reference implementation defines an answer there.
132 */
133template <class T>
134std::vector<std::size_t> kahn(const Matrix<T>& adj) {
135 const std::size_t n = adj.rows();
136 const T zero = num_traits<T>::from_int(0);
137 std::vector<std::size_t> indeg(n, 0);
138 for (std::size_t c = 0; c < n; ++c)
139 for (std::size_t r = 0; r < n; ++r)
140 if (adj(r, c) > zero) ++indeg[c];
141
142 std::deque<std::size_t> q;
143 for (std::size_t v = 0; v < n; ++v)
144 if (indeg[v] == 0) q.push_back(v);
145
146 std::vector<std::size_t> order;
147 order.reserve(n);
148 while (!q.empty()) {
149 const std::size_t i = q.front();
150 q.pop_front();
151 order.push_back(i);
152 for (std::size_t j = 0; j < n; ++j)
153 if (adj(i, j) > zero && --indeg[j] == 0) q.push_back(j);
154 }
155 return order;
156}
157
158} // namespace detail
159
160/**
161 * @brief Maximum sustainable multiplicity (concurrency level) of every
162 * element of a layered software network.
163 *
164 * @param lsn the call graph and the per-element attributes
165 * @return (n) maximum multiplicity sustainable by each element
166 */
167template <class T>
168std::vector<Multiplicity<T>> lsn_max_multiplicity(const LsnInput<T>& lsn) {
169 const std::size_t n = lsn.dag.rows();
170 if (lsn.dag.cols() != n) throw InputError("lsn_max_multiplicity: the call graph is not square");
171 if (lsn.type.size() != n || lsn.isref.size() != n)
172 throw InputError("lsn_max_multiplicity: type/isref disagree with the graph size");
173 if (!lsn.hassetup.empty() && lsn.hassetup.size() != n)
174 throw InputError("lsn_max_multiplicity: hassetup disagrees with the graph size");
175 if (!lsn.entry_has_arrival.empty() && lsn.entry_has_arrival.size() != n)
176 throw InputError("lsn_max_multiplicity: entry_has_arrival disagrees with the graph size");
177 if (!lsn.repl.empty() && lsn.repl.size() != n)
178 throw InputError("lsn_max_multiplicity: repl disagrees with the graph size");
179 auto rep = [&](std::size_t i) -> long { return lsn.repl.empty() ? 1L : std::max(1L, lsn.repl[i]); };
180
181 // an edge is any strictly positive weight, MATLAB's ag = lsn.dag > 0
182 const T zero = num_traits<T>::from_int(0);
183 Matrix<T> ag(n, n, zero);
184 for (std::size_t i = 0; i < n; ++i)
185 for (std::size_t j = 0; j < n; ++j)
186 if (lsn.dag(i, j) > zero) ag(i, j) = num_traits<T>::from_int(1);
187
188 const std::vector<std::size_t> order = detail::kahn(ag);
189 if (order.size() != n)
190 throw InputError("lsn_max_multiplicity: the call graph has a cycle, no topological order");
191
192 // multiplicities shorter than the graph are unbounded, as MATLAB pads with Inf
193 std::vector<Multiplicity<T>> mult = lsn.mult;
194 if (mult.size() > n) throw InputError("lsn_max_multiplicity: more multiplicities than elements");
195 while (mult.size() < n) mult.push_back(Multiplicity<T>::inf());
196
197 std::vector<Multiplicity<T>> inflow(n, Multiplicity<T>::of(0));
198 for (std::size_t i = 0; i < n; ++i) {
199 if (lsn.type[i] == LsnElementType::TASK && lsn.isref[i]) {
200 inflow[i] = mult_scale(mult[i], rep(i));
201 } else if (lsn.type[i] == LsnElementType::ENTRY && !lsn.entry_has_arrival.empty() &&
202 lsn.entry_has_arrival[i]) {
203 // an entry fed by an open arrival needs at least one thread of each
204 // replica of its parent task, which has an arrival stream each
205 inflow[i] = Multiplicity<T>::of(rep(i));
206 }
207 }
208
209 // outflow: what one replica holds (returned); total: what all replicas pass on
210 std::vector<Multiplicity<T>> outflow(n, Multiplicity<T>::of(0));
211 for (std::size_t k = 0; k < n; ++k) {
212 const std::size_t i = order[k];
213 const bool has_setup = !lsn.hassetup.empty() && lsn.hassetup[i];
214 Multiplicity<T> total;
215 if (has_setup && inflow[i].positive()) {
216 // setup-task multiplicity rationale: see _kb/03-api-layer.md (cpp port notes: lsn)
217 outflow[i] = mult[i];
218 total = mult_scale(mult[i], rep(i));
219 } else {
220 outflow[i] = mult_min(inflow[i], mult[i]);
221 total = mult_min(inflow[i], mult_scale(mult[i], rep(i)));
222 }
223 for (std::size_t j = 0; j < n; ++j) {
224 if (j == i || !(ag(i, j) > zero)) continue;
225 if (lsn.type[j] == LsnElementType::HOST && lsn.type[i] == LsnElementType::TASK) {
226 // a host replica runs r(task)/r(host) replicas of the task
227 const long per = std::max(1L, rep(i) / rep(j));
228 inflow[j] = mult_add(inflow[j], mult_min(total, mult_scale(outflow[i], per)));
229 } else {
230 inflow[j] = mult_add(inflow[j], total);
231 }
232 }
233 }
234
235 for (std::size_t i = 0; i < n; ++i)
236 if (lsn.type[i] == LsnElementType::TASK && mult[i].infinite && !lsn.isref[i])
237 outflow[i] = Multiplicity<T>::inf();
238
239 return outflow;
240}
241
242} // namespace lsn
243} // namespace line
244
245#endif // LINE_API_LQN_LSN_MAX_MULTIPLICITY_H
InputError(const std::string &what)
Definition error.h:105
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
LsnElementType
Element kinds, with the values of MATLAB's LayeredNetworkElement.
std::vector< Multiplicity< T > > lsn_max_multiplicity(const LsnInput< T > &lsn)
Maximum sustainable multiplicity (concurrency level) of every element of a layered software network.
Multiplicity< T > mult_add(const Multiplicity< T > &a, const Multiplicity< T > &b)
a + b, with infinity absorbing.
Multiplicity< T > mult_scale(const Multiplicity< T > &a, long k)
a times the integer k, with infinity absorbing.
Multiplicity< T > mult_min(const Multiplicity< T > &a, const Multiplicity< T > &b)
min(a,b), with infinity as the top element.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
The plain-data fields of a layered software network read by the algorithm.
std::vector< bool > hassetup
(n) setup task flag; may be empty
std::vector< bool > entry_has_arrival
(n) entry with an open arrival; may be empty
std::vector< LsnElementType > type
(n) element kind
std::vector< bool > isref
(n) reference task flag
Matrix< T > dag
(n x n) call graph; an edge is a strictly positive entry
std::vector< long > repl
(n) replication of each element (entries and activities: their task's); may be empty
std::vector< Multiplicity< T > > mult
(n) declared multiplicity; short vectors are padded with Inf
A multiplicity, possibly infinite; MATLAB's mult(i) = Inf.
static Multiplicity finite(const T &v)
static Multiplicity inf()
static Multiplicity of(long v)