LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_join_droprate.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_SN_SN_JOIN_DROPRATE_H
6#define LINE_API_SN_SN_JOIN_DROPRATE_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Quorum joins: how many siblings a Join fires on, and what that drops.
12 *
13 * `sn_join_quorum` reports the quorum of a Join node and `sn_join_droprate` the
14 * rate at which forked siblings are discarded because the join already fired.
15 * Under a quorum of k out of n, the n-k slowest siblings never reach the join
16 * and their work is lost; a standard join has a quorum of n and drops
17 * nothing.
18 */
19
20#include <cmath>
21#include <cstddef>
22#include <map>
23
25#include "line/num/number.h"
26
27namespace line {
28namespace sn {
29
30/**
31 * The number of siblings the Join node `joinNode` (1-based) fires on, out of
32 * `nbranches` forked. Port of `matlab/src/api/fj/sn_join_quorum.m`.
33 *
34 * A standard join, an absent declaration, a non-positive quorum and a quorum
35 * that is not smaller than the sibling count all return `nbranches`, the
36 * ordinary AND-join.
37 *
38 * NOTE the index space: `joindecl` is keyed by the BASE node index and carries
39 * ONE quorum per join rather than one per class, so a multiclass model with a
40 * different quorum per class cannot be expressed here. The reference and the
41 * JSON interchange have the same limitation.
42 */
43template <class T>
44std::size_t sn_join_quorum(const qn::NetworkStruct<T>& sn, std::size_t joinNode,
45 std::size_t nbranches) {
46 if (joinNode == 0) return nbranches;
47 typename std::map<std::size_t, typename qn::NetworkStruct<T>::JoinDecl>::const_iterator it =
48 sn.joindecl.find(joinNode);
49 if (it == sn.joindecl.end()) return nbranches;
50 if (it->second.strategy == lang::JoinStrategy::STD) return nbranches;
51 const double q = it->second.quorum;
52 if (q > 0.0) {
53 const std::size_t k = static_cast<std::size_t>(q + 0.5);
54 if (k > 0 && k < nbranches) return k;
55 }
56 return nbranches;
57}
58
59/**
60 * Rate at which sibling tasks are discarded at each Join, as an
61 * (nstations x nclasses) matrix that is zero away from the Join rows.
62 * Port of `matlab/src/api/fj/sn_join_droprate.m`.
63 *
64 * A Join is the one station where the loss identity ArvR - Tput does NOT hold,
65 * because the two rates are in different units: AN counts the SIBLINGS offered
66 * to the join (N per parent job) while TN counts the PARENT jobs released by it
67 * (one per synchronisation). Reading ArvR - Tput there reports (N-1)/N of the
68 * offered traffic as lost at every join, standard joins included, when a
69 * standard join loses nothing at all.
70 *
71 * The siblings a join actually consumes are K per synchronisation, where K is
72 * the quorum (K = N on a standard join), so
73 *
74 * DropRateJoin = max(0, AN - K*TN)
75 *
76 * which is 0 for a standard join and (N-K)*TN for a quorum, the rate at which
77 * the stragglers of an already-fired parent are discarded on arrival.
78 *
79 * This is the DERIVED value, exact given TN and AN. A solver that MEASURES the
80 * discards on its own sample path reports its own: the LDES engine fills
81 * `LdesResult::DropRateJoin` from the sibling it actually threw away, and a
82 * measured zero is a better answer than this one, which carries the
83 * finite-sample gap between the two rates.
84 */
85template <class T>
87 const Matrix<T>& AN) {
88 const std::size_t M = sn.stations.size(), K = sn.classes.size();
89 const T zero = num_traits<T>::from_int(0);
90 Matrix<T> out(M, K, zero);
91 if (sn.fj.empty() || TN.rows() == 0 || AN.rows() == 0) return out;
92 for (std::size_t ind = 1; ind <= sn.nodes.size(); ++ind) {
93 if (sn.nodes[ind - 1].nodetype != qn::NodeType::Join) continue;
94 const std::size_t ist = sn.nodes[ind - 1].station;
95 if (ist == 0 || ist > M) continue;
96 for (std::size_t r = 0; r < K; ++r) {
97 if (ist - 1 >= AN.rows() || r >= AN.cols()) continue;
98 const T a = AN(ist - 1, r);
99 const T t = (ist - 1 < TN.rows() && r < TN.cols()) ? TN(ist - 1, r) : zero;
100 if (!std::isfinite(num_traits<T>::to_double(a))) continue;
101 if (!std::isfinite(num_traits<T>::to_double(t))) continue;
102 if (!(a > zero)) continue;
103 // PER CLASS: a variable forking level makes the sibling count differ
104 // between classes, so it cannot be hoisted out of this loop.
105 const std::size_t nsib = sn.join_siblings(ind, r + 1);
106 if (nsib == 0) continue;
107 const std::size_t kreq = sn_join_quorum(sn, ind, nsib);
108 const T d = T(a - num_traits<T>::from_int(static_cast<long>(kreq)) * t);
109 out(ist - 1, r) = (d > zero) ? d : zero;
110 }
111 }
112 return out;
113}
114
115} // namespace sn
116} // namespace line
117
118#endif // LINE_API_SN_SN_JOIN_DROPRATE_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
A network plus its refreshed NetworkStruct.
Matrix< T > sn_join_droprate(const qn::NetworkStruct< T > &sn, const Matrix< T > &TN, const Matrix< T > &AN)
Rate at which sibling tasks are discarded at each Join, as an (nstations x nclasses) matrix that is z...
std::size_t sn_join_quorum(const qn::NetworkStruct< T > &sn, std::size_t joinNode, std::size_t nbranches)
The number of siblings the Join node joinNode (1-based) fires on, out of nbranches forked.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.