5#ifndef LINE_API_LQN_LQN_REF_ROUTES_H
6#define LINE_API_LQN_LQN_REF_ROUTES_H
67 std::vector<std::vector<std::pair<std::size_t, T>>>
actweight;
68 std::vector<LqnRefCall<T>>
calls;
73 double poolmin = std::numeric_limits<double>::infinity();
88 std::size_t cidx, to, aidx;
94std::string lqn_ref_name(
const LqnStruct<T>&
lqn, std::size_t idx) {
95 if (idx <
lqn.hashnames.size() && !
lqn.hashnames[idx].empty())
return lqn.hashnames[idx];
96 return "#" + std::to_string(idx);
100inline std::string lqn_ref_fmt_g(
double x) {
102 std::snprintf(buf,
sizeof(buf),
"%g", x);
108T lqn_ref_call_mean(
const LqnStruct<T>& lqn, std::size_t cidx) {
109 if (cidx < lqn.callproc_mean.size()) {
110 const T m = lqn.callproc_mean[cidx];
111 if (std::isfinite(num_traits<T>::to_double(m)))
return m;
113 return num_traits<T>::from_int(1);
122std::vector<std::vector<LqnRefSucc<T>>> lqn_ref_sync_successors(
const LqnStruct<T>& lqn) {
123 std::vector<std::vector<LqnRefSucc<T>>> succ(lqn.nidx + 1);
124 std::vector<std::size_t> entry_of_act(lqn.nidx + 1, 0);
125 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
126 const std::size_t eidx = lqn.eshift + e;
127 for (std::size_t a : lqn.actsof[eidx])
128 if (a <= lqn.nidx) entry_of_act[a] = eidx;
130 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
132 const std::size_t aidx = lqn.callpair_src[cidx];
133 const std::size_t eto = lqn.callpair_dst[cidx];
134 if (aidx < 1 || eto < 1 || aidx > lqn.nidx)
continue;
135 const std::size_t efrom = entry_of_act[aidx];
136 if (efrom < 1)
continue;
137 succ[efrom].push_back({cidx, eto, aidx, lqn_ref_call_mean(lqn, cidx)});
144std::vector<bool> lqn_ref_reachable(
const LqnStruct<T>& lqn,
145 const std::vector<std::vector<LqnRefSucc<T>>>& succ,
147 std::vector<bool> seen(lqn.nidx + 1,
false);
148 std::vector<std::size_t> stack(lqn.entriesof[tidx].begin(), lqn.entriesof[tidx].end());
149 while (!stack.empty()) {
150 const std::size_t e = stack.back();
152 if (seen[e])
continue;
154 for (
const LqnRefSucc<T>& s : succ[e]) stack.push_back(s.to);
165std::vector<std::pair<std::size_t, T>> lqn_ref_act_weights(
const LqnStruct<T>& lqn,
166 std::size_t eidx, std::string& why) {
167 std::vector<std::pair<std::size_t, T>> aw;
168 const std::vector<std::size_t>& acts = lqn.actsof[eidx];
169 if (acts.empty())
return aw;
170 const T zero = num_traits<T>::from_int(0);
171 std::vector<std::size_t> nodeset;
172 nodeset.push_back(eidx);
173 nodeset.insert(nodeset.end(), acts.begin(), acts.end());
174 const std::size_t n = nodeset.size();
175 std::vector<std::size_t> pos(lqn.nidx + 1, 0);
176 for (std::size_t i = 0; i < n; ++i) pos[nodeset[i]] = i + 1;
177 std::vector<std::vector<T>> A(n, std::vector<T>(n, zero));
178 for (std::size_t i = 0; i < n; ++i)
179 for (std::size_t v : lqn.graph.succ(nodeset[i]))
180 if (v <= lqn.nidx && pos[v] > 0) A[i][pos[v] - 1] = lqn.graph.get(nodeset[i], v);
181 std::vector<long> remaining(n, 0);
182 for (std::size_t j = 0; j < n; ++j)
183 for (std::size_t i = 0; i < n; ++i)
184 if (A[i][j] > zero) ++remaining[j];
186 std::vector<T> w(n, zero);
187 w[0] = num_traits<T>::from_int(1);
188 std::vector<std::size_t> queue;
189 for (std::size_t j = 0; j < n; ++j)
190 if (remaining[j] == 0) queue.push_back(j);
191 std::vector<bool> done(n,
false);
192 std::size_t ndone = 0, head = 0;
193 while (head < queue.size()) {
194 const std::size_t i = queue[head++];
195 if (done[i])
continue;
198 for (std::size_t j = 0; j < n; ++j) {
199 if (!(A[i][j] > zero))
continue;
200 w[j] = T(w[j] + w[i] * A[i][j]);
202 if (remaining[j] <= 0 && !done[j]) queue.push_back(j);
206 why =
"the activity graph of entry '" + lqn_ref_name(lqn, eidx) +
"' contains a loop";
209 for (std::size_t i = 1; i < n; ++i) aw.emplace_back(nodeset[i], w[i]);
215bool lqn_ref_build_group(
const LqnStruct<T>& lqn,
216 const std::vector<std::vector<LqnRefSucc<T>>>& succ, std::size_t r,
217 const std::vector<bool>& is_caller,
double maxpaths,
218 const std::vector<std::size_t>& server_set, LqnRefGroup<T>& g,
220 const T zero = num_traits<T>::from_int(0);
221 const std::vector<std::size_t>& roots = lqn.entriesof[r];
222 if (roots.empty())
return false;
227 enum :
int { WHITE = 0, GREY = 1, BLACK = 2 };
228 std::vector<int> color(lqn.nidx + 1, WHITE);
229 std::vector<std::size_t> post;
230 for (std::size_t e0 : roots) {
231 if (color[e0] != WHITE)
continue;
232 std::vector<std::pair<std::size_t, std::size_t>> stack{{e0, 0}};
233 while (!stack.empty()) {
234 const std::size_t u = stack.back().first;
235 const std::size_t ci = stack.back().second;
236 if (ci == 0) color[u] = GREY;
237 if (ci < succ[u].size()) {
238 stack.back().second = ci + 1;
239 const std::size_t v = succ[u][ci].to;
240 if (color[v] == GREY) {
241 why =
"the synchronous call graph below '" + lqn_ref_name(lqn, v) +
244 }
else if (color[v] == WHITE) {
245 stack.emplace_back(v, 0);
254 const std::vector<std::size_t> entries(post.rbegin(), post.rend());
255 if (entries.empty())
return false;
256 const std::size_t nE = entries.size();
257 std::vector<std::size_t> pos(lqn.nidx + 1, 0);
258 for (std::size_t i = 0; i < nE; ++i) pos[entries[i]] = i + 1;
259 std::vector<std::size_t> etask(nE);
260 std::vector<bool> ismem(nE);
262 for (std::size_t i = 0; i < nE; ++i) {
263 etask[i] = lqn.parent[entries[i]];
264 ismem[i] = is_caller[etask[i]];
265 anymem = anymem || ismem[i];
267 if (!anymem)
return false;
271 std::vector<std::vector<std::pair<std::size_t, T>>> actweight(nE);
272 std::vector<LqnRefCall<T>> calls;
273 for (std::size_t i = 0; i < nE; ++i) {
274 const std::size_t u = entries[i];
276 actweight[i] = lqn_ref_act_weights(lqn, u, awwhy);
277 if (!awwhy.empty()) {
281 for (
const LqnRefSucc<T>& s : succ[u]) {
282 if (pos[s.to] == 0)
continue;
284 for (
const std::pair<std::size_t, T>& aw : actweight[i])
285 if (aw.first == s.aidx) {
289 calls.push_back({s.cidx, i, pos[s.to] - 1, s.aidx, T(w * s.mean)});
294 std::vector<T> ventry(nE, zero);
295 std::vector<std::size_t> rootpos;
296 for (std::size_t e : roots)
297 if (pos[e] > 0) rootpos.push_back(pos[e] - 1);
299 T(num_traits<T>::from_int(1) / num_traits<T>::from_int(
int(roots.size())));
300 for (std::size_t p : rootpos) ventry[p] = rootshare;
301 for (std::size_t i = 0; i < nE; ++i)
302 for (
const LqnRefCall<T>& c : calls)
303 if (c.from == i) ventry[c.to] = T(ventry[c.to] + ventry[i] * c.vcall);
304 for (LqnRefCall<T>& c : calls) c.vcall = T(ventry[c.from] * c.vcall);
307 std::vector<bool> in_prefix(nE,
false), pref_term(nE,
false);
308 std::vector<double> npath(nE, 0.0);
309 for (std::size_t p : rootpos) {
313 for (std::size_t i = 0; i < nE; ++i) {
314 if (!in_prefix[i])
continue;
320 for (std::size_t s : server_set)
322 why =
"task '" + lqn_ref_name(lqn, etask[i]) +
323 "' is both an intermediate on the reference path and a server of this "
327 for (
const LqnRefCall<T>& c : calls)
329 in_prefix[c.to] =
true;
330 npath[c.to] += npath[i];
334 for (std::size_t i = 0; i < nE; ++i)
335 if (pref_term[i]) np += npath[i];
337 why =
"the reference path into this layer carries " + lqn_ref_fmt_g(np) +
338 " distinct routes, above config.interlock_maxpaths = " + lqn_ref_fmt_g(maxpaths);
342 g = LqnRefGroup<T>();
344 g.head_is_caller = is_caller[r];
345 for (std::size_t i = 0; i < nE; ++i) {
346 if (!ismem[i])
continue;
348 for (std::size_t t : g.members) seen = seen || t == etask[i];
349 if (!seen) g.members.push_back(etask[i]);
355 g.actweight = actweight;
357 for (std::size_t i = 0; i < nE; ++i)
359 g.prefix_pos.push_back(i);
360 g.prefix_term.push_back(pref_term[i]);
364 for (std::size_t t : etask)
365 if (t > 0 && t < lqn.maxmult.size()) g.poolmin = std::min(g.poolmin, lqn.maxmult[t]);
385 double maxpaths = 32.0,
386 const std::vector<std::size_t>& server_set = {}) {
388 if (
lqn.ncalls == 0 || callers.empty())
return out;
389 std::vector<bool> is_caller(
lqn.nidx + 1,
false);
390 for (std::size_t c : callers)
391 if (c <=
lqn.nidx) is_caller[c] =
true;
392 const std::vector<std::vector<detail::LqnRefSucc<T>>> succ =
393 detail::lqn_ref_sync_successors(
lqn);
395 std::vector<std::size_t> reftasks;
396 for (std::size_t t = 1; t <=
lqn.ntasks; ++t)
397 if (
lqn.isref[
lqn.tshift + t]) reftasks.push_back(
lqn.tshift + t);
398 if (reftasks.empty())
return out;
400 std::vector<int> nref_of(
lqn.nidx + 1, 0);
401 for (std::size_t r : reftasks) {
402 const std::vector<bool> seen = detail::lqn_ref_reachable(
lqn, succ, r);
403 std::vector<bool> mem(
lqn.nidx + 1,
false);
404 for (std::size_t e = 1; e <=
lqn.nidx; ++e)
405 if (seen[e] && is_caller[
lqn.parent[e]]) mem[
lqn.parent[e]] =
true;
406 if (is_caller[r]) mem[r] =
true;
407 for (std::size_t t = 1; t <=
lqn.nidx; ++t)
408 if (mem[t]) ++nref_of[t];
410 for (std::size_t t = 1; t <= lqn.
nidx; ++t)
411 if (nref_of[t] > 1) {
412 out.why =
"task '" + detail::lqn_ref_name(lqn, t) +
"' is reachable from " +
413 std::to_string(nref_of[t]) +
414 " reference tasks, whose customer pools are independent";
418 for (std::size_t r : reftasks) {
422 detail::lqn_ref_build_group(lqn, succ, r, is_caller, maxpaths, server_set, g, gwhy);
428 if (ok) out.groups.push_back(std::move(g));
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
LayeredNetworkStruct, the flattened description of a layered queueing network.
LqnRefRoutes< T > lqn_ref_routes(const LqnStruct< T > &lqn, const std::vector< std::size_t > &callers, double maxpaths=32.0, const std::vector< std::size_t > &server_set={})
Resolve the reference routes into the layer whose callers are CALLERS.
Conservation laws of a layered queueing network, enumerated from its structure.
Number-type abstraction for the templated API port.
One row of calls: the reference's [cidx, fromPos, toPos, aidx, vCall].
std::size_t aidx
activity issuing the call
std::size_t cidx
call index
T vcall
mean invocations of the call per REF cycle
std::size_t from
0-based position of the calling entry in entries
std::size_t to
0-based position of the called entry in entries
One group, the reference's R(g): the DAG below one REF task.
std::vector< T > ventry
mean invocations of each entry per REF cycle
std::vector< std::size_t > members
callers of the layer on the DAG, descent order
std::vector< bool > prefix_term
that prefix position is a first caller
std::vector< std::size_t > entries
every DAG entry, topological order, roots first
double poolmin
min of lqn.maxmult over the DAG tasks. A DIAGNOSTIC: the chain population is never capped by it.
bool head_is_caller
the REF task is itself a caller of the layer
double npaths
distinct REF-to-caller routes, counted
std::vector< std::size_t > etask
lqn.parent of each entry
std::vector< std::size_t > prefix_pos
0-based positions forming the prefix, topological order
std::size_t reftask
task index of the REF task at the root
std::vector< bool > ismember
that entry's task is a caller of the layer
std::vector< LqnRefCall< T > > calls
std::vector< std::vector< std::pair< std::size_t, T > > > actweight
per entry, (aidx, executions per invocation of the entry)
What lqn_ref_routes returns: the groups, or a non-empty why and no groups.
std::vector< LqnRefGroup< T > > groups
std::string why
non-empty when the layer must fall back to another interlock method