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 * SCOPE: the port takes the six plain-data fields the algorithm actually
28 * reads -- the call graph, the multiplicities, the element types, the
29 * reference and setup-task flags, and the per-entry open-arrival flag --
30 * rather than a LayeredNetworkStruct, so no model layer is needed.
31 *
32 * DIVERGENCE, MATLAB vs JAR: MATLAB also seeds inflow(i) = 1 for an ENTRY that
33 * has an open arrival (lsn.arrival{i} non-empty); the JAR omits that branch
34 * entirely, so an open-arrival entry reachable from no reference task gets
35 * outflow 0 there and 1 in MATLAB. This port follows MATLAB, the reference
36 * implementation, and exposes the flag as entry_has_arrival.
37 *
38 * ARITHMETIC: comparisons and additions only, so a finite field computation,
39 * exact in the exact instantiation. Infinite multiplicity is carried by an
40 * explicit flag instead of a floating infinity, both because Rational has no
41 * infinity and because Inf + Inf and min(Inf, Inf) are then decided by the
42 * algorithm rather than by the number type.
43 */
44
45#include <cstddef>
46#include <deque>
47#include <vector>
48
49#include "line/num/number.h"
50#include "line/util/error.h"
51#include "line/util/matrix.h"
52
53namespace line {
54namespace lsn {
55
56/** Element kinds, with the values of MATLAB's LayeredNetworkElement. */
57enum class LsnElementType { HOST = 0, TASK = 1, ENTRY = 2, ACTIVITY = 3, CALL = 4 };
58
59/** A multiplicity, possibly infinite; MATLAB's mult(i) = Inf. */
60template <class T>
62 bool infinite = false;
64
65 static Multiplicity finite(const T& v) {
67 r.infinite = false;
68 r.value = v;
69 return r;
70 }
71 static Multiplicity of(long v) { return finite(num_traits<T>::from_int(v)); }
72 static Multiplicity inf() {
74 r.infinite = true;
75 return r;
76 }
77
78 bool positive() const { return infinite || value > num_traits<T>::from_int(0); }
79};
80
81/** a + b, with infinity absorbing. */
82template <class T>
87
88/** min(a,b), with infinity as the top element. */
89template <class T>
91 if (a.infinite) return b;
92 if (b.infinite) return a;
93 return a.value < b.value ? a : b;
94}
95
96/** The plain-data fields of a layered software network read by the algorithm. */
97template <class T>
98struct LsnInput {
99 Matrix<T> dag; ///< (n x n) call graph; an edge is a strictly positive entry
100 std::vector<Multiplicity<T>> mult; ///< (n) declared multiplicity; short vectors are padded with Inf
101 std::vector<LsnElementType> type; ///< (n) element kind
102 std::vector<bool> isref; ///< (n) reference task flag
103 std::vector<bool> hassetup; ///< (n) setup task flag; may be empty
104 std::vector<bool> entry_has_arrival; ///< (n) entry with an open arrival; may be empty
105};
106
107namespace detail {
108
109/**
110 * Kahn topological sort of an adjacency matrix, port of matlab/util/kahn.m.
111 * The queue is FIFO, as in both MATLAB and the JAR, so the order is identical.
112 * A cycle leaves the order short, which the caller must reject: MATLAB then
113 * indexes with a zero and the JAR runs off the end of its list, so neither
114 * reference implementation defines an answer there.
115 */
116template <class T>
117std::vector<std::size_t> kahn(const Matrix<T>& adj) {
118 const std::size_t n = adj.rows();
119 const T zero = num_traits<T>::from_int(0);
120 std::vector<std::size_t> indeg(n, 0);
121 for (std::size_t c = 0; c < n; ++c)
122 for (std::size_t r = 0; r < n; ++r)
123 if (adj(r, c) > zero) ++indeg[c];
124
125 std::deque<std::size_t> q;
126 for (std::size_t v = 0; v < n; ++v)
127 if (indeg[v] == 0) q.push_back(v);
128
129 std::vector<std::size_t> order;
130 order.reserve(n);
131 while (!q.empty()) {
132 const std::size_t i = q.front();
133 q.pop_front();
134 order.push_back(i);
135 for (std::size_t j = 0; j < n; ++j)
136 if (adj(i, j) > zero && --indeg[j] == 0) q.push_back(j);
137 }
138 return order;
139}
140
141} // namespace detail
142
143/**
144 * @brief Maximum sustainable multiplicity (concurrency level) of every
145 * element of a layered software network.
146 *
147 * @param lsn the call graph and the per-element attributes
148 * @return (n) maximum multiplicity sustainable by each element
149 */
150template <class T>
151std::vector<Multiplicity<T>> lsn_max_multiplicity(const LsnInput<T>& lsn) {
152 const std::size_t n = lsn.dag.rows();
153 if (lsn.dag.cols() != n) throw InputError("lsn_max_multiplicity: the call graph is not square");
154 if (lsn.type.size() != n || lsn.isref.size() != n)
155 throw InputError("lsn_max_multiplicity: type/isref disagree with the graph size");
156 if (!lsn.hassetup.empty() && lsn.hassetup.size() != n)
157 throw InputError("lsn_max_multiplicity: hassetup disagrees with the graph size");
158 if (!lsn.entry_has_arrival.empty() && lsn.entry_has_arrival.size() != n)
159 throw InputError("lsn_max_multiplicity: entry_has_arrival disagrees with the graph size");
160
161 // an edge is any strictly positive weight, MATLAB's ag = lsn.dag > 0
162 const T zero = num_traits<T>::from_int(0);
163 Matrix<T> ag(n, n, zero);
164 for (std::size_t i = 0; i < n; ++i)
165 for (std::size_t j = 0; j < n; ++j)
166 if (lsn.dag(i, j) > zero) ag(i, j) = num_traits<T>::from_int(1);
167
168 const std::vector<std::size_t> order = detail::kahn(ag);
169 if (order.size() != n)
170 throw InputError("lsn_max_multiplicity: the call graph has a cycle, no topological order");
171
172 // multiplicities shorter than the graph are unbounded, as MATLAB pads with Inf
173 std::vector<Multiplicity<T>> mult = lsn.mult;
174 if (mult.size() > n) throw InputError("lsn_max_multiplicity: more multiplicities than elements");
175 while (mult.size() < n) mult.push_back(Multiplicity<T>::inf());
176
177 std::vector<Multiplicity<T>> inflow(n, Multiplicity<T>::of(0));
178 for (std::size_t i = 0; i < n; ++i) {
179 if (lsn.type[i] == LsnElementType::TASK && lsn.isref[i]) {
180 inflow[i] = mult[i];
181 } else if (lsn.type[i] == LsnElementType::ENTRY && !lsn.entry_has_arrival.empty() &&
182 lsn.entry_has_arrival[i]) {
183 // an entry fed by an open arrival needs at least one thread of its
184 // parent task (MATLAB only; the JAR has no such branch)
185 inflow[i] = Multiplicity<T>::of(1);
186 }
187 }
188
189 std::vector<Multiplicity<T>> outflow(n, Multiplicity<T>::of(0));
190 for (std::size_t k = 0; k < n; ++k) {
191 const std::size_t i = order[k];
192 const bool has_setup = !lsn.hassetup.empty() && lsn.hassetup[i];
193 if (has_setup && inflow[i].positive()) {
194 // setup-task multiplicity rationale: see _kb/03-api-layer.md (cpp port notes: lsn)
195 outflow[i] = mult[i];
196 } else {
197 outflow[i] = mult_min(inflow[i], mult[i]);
198 }
199 for (std::size_t j = 0; j < n; ++j)
200 if (j != i && ag(i, j) > zero) inflow[j] = mult_add(inflow[j], outflow[i]);
201 }
202
203 for (std::size_t i = 0; i < n; ++i)
204 if (lsn.type[i] == LsnElementType::TASK && mult[i].infinite && !lsn.isref[i])
205 outflow[i] = Multiplicity<T>::inf();
206
207 return outflow;
208}
209
210} // namespace lsn
211} // namespace line
212
213#endif // LINE_API_LQN_LSN_MAX_MULTIPLICITY_H
InputError(const std::string &what)
Definition error.h:39
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_min(const Multiplicity< T > &a, const Multiplicity< T > &b)
min(a,b), with infinity as the top element.
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< 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)