LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_node_metrics.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_SN_SN_NODE_METRICS_H
6#define LINE_API_SN_SN_NODE_METRICS_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Ports of matlab/src/api/sn/sn_get_node_arvr_from_tput.m and
12 * sn_get_node_tput_from_tput.m: the NODE-level arrival rate and throughput
13 * tables behind `getAvgNode`, `getAvgNodeTable` and the node chain getters.
14 *
15 * Station metrics are what a solver returns; node metrics are what the user
16 * asks for when the model has nodes that are not stations -- a Router, a
17 * ClassSwitch, a Cache, a Fork or a Join. Those nodes have no service and no
18 * queue, so their only meaningful averages are the rates flowing through them,
19 * and those come from the station throughputs plus the NODE-level routing
20 * `sn.rtnodes` and the per-chain node visits.
21 *
22 * REFERENCE STATION INDEX. Both functions read `sn.refstat(c)` with c a CHAIN
23 * index, while `sn.refstat` is indexed by CLASS. The two agree whenever the
24 * chain's classes are numbered from its own index, which every single-chain
25 * and every non-switching model satisfies, and the refresh already refuses a
26 * chain whose classes disagree on their reference station. Reproduced as the
27 * reference has it: correcting the index here alone would make these tables
28 * disagree with MATLAB, the JAR and native Python at once.
29 *
30 * CACHE HIT AND MISS RATES. The reference splits a Cache node's flow with the
31 * ACTUAL hit and miss probabilities the cache fixed point converged to, which
32 * this struct does not store on the node -- `refresh_cacheqn_actual_visits`
33 * folds them into the visits instead. They are therefore optional arguments:
34 * a caller holding the converged probabilities passes them and gets the
35 * reference's first branch, and a caller that does not gets its second, the
36 * visit-ratio split, which is what the folded visits already encode.
37 *
38 * ARITHMETIC: field. Sums and quotients only.
39 */
40
41#include <cstddef>
42#include <map>
43#include <vector>
44
46#include "line/num/number.h"
47
48namespace line {
49namespace api {
50
51/**
52 * The converged cache split, per ORIGINAL class of one Cache node.
53 *
54 * Empty vectors mean "not available", which selects the reference's
55 * visit-ratio branch. `delayed_hit` may stay empty even when the other two are
56 * given; it is zero then, as the reference defaults it.
57 */
58template <class T>
60 std::vector<T> hit, miss, delayed_hit;
61};
62
63namespace detail {
64
65/** True when class `r` (1-based) is a hit or miss class of ANY Cache node. */
66template <class T>
67bool sn_is_any_cache_class(const qn::NetworkStruct<T>& sn, std::size_t r) {
68 for (const auto& kv : sn.nodeparam) {
69 const qn::CacheParam<T>& cp = kv.second;
70 for (std::size_t x : cp.hitclass)
71 if (x == r) return true;
72 for (std::size_t x : cp.missclass)
73 if (x == r) return true;
74 }
75 return false;
76}
77
78/**
79 * `sum(TN(refstat, inchain))`, the chain's total throughput at its reference
80 * station, over the classes that HAVE a reference visit.
81 *
82 * THIS SUM AND `sn_chain_refvisits` ARE THE TWO HALVES OF ONE RATIO -- a rate
83 * per reference visit -- so they have to run over the same classes or the
84 * quotient is not that rate. The restriction is inert on every model but a
85 * cache one, because a class with no visit at a station has no throughput there
86 * either. A CACHE IS THE ONE EXCEPTION: its over-route sends a hit or miss
87 * class THROUGH a station that never serves it, `getAvgHandles` keeps the Tput
88 * handle of a cache class so that pass-through flow IS reported, and the
89 * refreshed struct gives the same pair no visit. The reference never has the
90 * term at all -- its implicit ClassSwitch absorbs the switch before the station
91 * -- so counting it here alone scaled every cache node rate by the ratio of the
92 * two sums: exactly 2x on cache_replc_fifo, whose Delay carries the whole
93 * hit+miss flow a second time.
94 */
95template <class T>
96T sn_chain_tput(const qn::NetworkStruct<T>& sn, const Matrix<T>& TN, std::size_t c,
97 std::size_t refstat, const std::vector<std::size_t>& inchain) {
98 T acc = num_traits<T>::from_int(0);
99 if (refstat == 0 || refstat > sn.nstations) return acc;
100 const T zero = num_traits<T>::from_int(0);
101 const bool have_visits = c < sn.visits.size();
102 const std::size_t isf = have_visits ? sn.stateful_of_station(refstat) - 1 : 0;
103 for (std::size_t r : inchain) {
104 if (have_visits && sn.visits[c](isf, r - 1) == zero && sn_is_any_cache_class(sn, r))
105 continue;
106 acc = T(acc + TN(refstat - 1, r - 1));
107 }
108 return acc;
109}
110
111/** `sum(sn.visits{c}(stationToStateful(refstat), inchain))`. */
112template <class T>
113T sn_chain_refvisits(const qn::NetworkStruct<T>& sn, std::size_t c, std::size_t refstat,
114 const std::vector<std::size_t>& inchain) {
115 T acc = num_traits<T>::from_int(0);
116 if (refstat == 0 || refstat > sn.nstations || c >= sn.visits.size()) return acc;
117 const std::size_t isf = sn.stateful_of_station(refstat) - 1;
118 for (std::size_t r : inchain) acc = T(acc + sn.visits[c](isf, r - 1));
119 return acc;
120}
121
122/**
123 * `sn.refstat(c)` as the reference spells it: the CLASS-indexed reference
124 * station read at the chain's index. See the file header.
125 */
126template <class T>
127std::size_t sn_chain_refstat(const qn::NetworkStruct<T>& sn, std::size_t c) {
128 if (c >= sn.classes.size()) return 0;
129 return sn.classes[c].refstat;
130}
131
132/** True when class `r` (1-based) is one of the node's hit or miss classes. */
133inline bool sn_is_cache_class(const std::vector<std::size_t>& hit,
134 const std::vector<std::size_t>& miss, std::size_t r) {
135 for (std::size_t x : hit)
136 if (x == r) return true;
137 for (std::size_t x : miss)
138 if (x == r) return true;
139 return false;
140}
141
142} // namespace detail
143
144/**
145 * Port of sn_get_node_arvr_from_tput.
146 *
147 * `AN` is the STATION arrival-rate table (sn_get_arvr_from_tput). Stations
148 * copy their own row; every other node takes the chain's reference throughput
149 * scaled by its node visits.
150 */
151template <class T>
153 const Matrix<T>& AN) {
154 const T zero = num_traits<T>::from_int(0);
155 const std::size_t I = sn.nodes.size(), C = sn.nchains, M = sn.nstations, R = sn.nclasses;
156 Matrix<T> ANn(I, R, zero);
157 if (TN.rows() == 0 || AN.rows() == 0) return ANn;
158
159 for (std::size_t ist = 0; ist < M; ++ist) {
160 const std::size_t ind = sn.station_to_node[ist];
161 if (ind == 0) continue;
162 for (std::size_t r = 0; r < R; ++r) ANn(ind - 1, r) = AN(ist, r);
163 }
164 for (std::size_t ind = 1; ind <= I; ++ind) {
165 if (sn.nodes[ind - 1].nodetype == qn::NodeType::Source) continue;
166 const bool is_cache = sn.nodes[ind - 1].nodetype == qn::NodeType::Cache;
167 const bool is_station = sn.nodes[ind - 1].station != 0;
168 for (std::size_t c = 0; c < C; ++c) {
169 const std::vector<std::size_t>& inchain = sn.inchain[c];
170 const std::size_t refstat = detail::sn_chain_refstat(sn, c);
171 const T den = detail::sn_chain_refvisits(sn, c, refstat, inchain);
172 if (!(den > zero)) continue;
173 const T tot = detail::sn_chain_tput(sn, TN, c, refstat, inchain);
174 for (std::size_t r : inchain) {
175 if (is_cache) {
176 typename std::map<std::size_t, qn::CacheParam<T>>::const_iterator it =
177 sn.nodeparam.find(ind);
178 if (it != sn.nodeparam.end() &&
179 detail::sn_is_cache_class(it->second.hitclass, it->second.missclass, r))
180 continue;
181 } else if (is_station) {
182 continue;
183 }
184 ANn(ind - 1, r - 1) = T(sn.nodevisits[c](ind - 1, r - 1) / den * tot);
185 }
186 }
187 }
188 return ANn;
189}
190
191/**
192 * Port of sn_get_node_tput_from_tput.
193 *
194 * Three passes, in the reference's order. A Cache node first splits the
195 * arriving flow into its hit and miss classes; every station then overwrites
196 * its own node row with its station throughput; finally every non-station node
197 * that is not a Sink accumulates what `sn.rtnodes` carries out of it.
198 *
199 * `cache_prob` maps a 1-based Cache NODE index to its converged split; an
200 * absent or empty entry selects the visit-ratio branch.
201 */
202template <class T>
204 const qn::NetworkStruct<T>& sn, const Matrix<T>& TN, const Matrix<T>& ANn,
205 const std::map<std::size_t, CacheActualProb<T>>& cache_prob =
206 std::map<std::size_t, CacheActualProb<T>>()) {
207 const T zero = num_traits<T>::from_int(0);
208 const std::size_t I = sn.nodes.size(), C = sn.nchains, M = sn.nstations, R = sn.nclasses;
209 Matrix<T> TNn(I, R, zero);
210 if (TN.rows() == 0) return TNn;
211
212 // pass 1: the Cache hit and miss split
213 for (std::size_t ind = 1; ind <= I; ++ind) {
214 if (sn.nodes[ind - 1].nodetype != qn::NodeType::Cache) continue;
215 typename std::map<std::size_t, qn::CacheParam<T>>::const_iterator np =
216 sn.nodeparam.find(ind);
217 if (np == sn.nodeparam.end()) continue;
218 const std::vector<std::size_t>& hitclass = np->second.hitclass;
219 const std::vector<std::size_t>& missclass = np->second.missclass;
220 typename std::map<std::size_t, CacheActualProb<T>>::const_iterator cp =
221 cache_prob.find(ind);
222 const bool have_actual = cp != cache_prob.end() && !cp->second.hit.empty();
223 for (std::size_t c = 0; c < C; ++c) {
224 const std::vector<std::size_t>& inchain = sn.inchain[c];
225 const std::size_t refstat = detail::sn_chain_refstat(sn, c);
226 const T tot = detail::sn_chain_tput(sn, TN, c, refstat, inchain);
227 for (std::size_t r : inchain) {
228 if (have_actual) {
229 for (std::size_t o = 0; o < hitclass.size(); ++o) {
230 T arv = zero;
231 if (ANn.rows() == I && o < R) {
232 arv = ANn(ind - 1, o);
233 } else {
234 bool in = false;
235 for (std::size_t q : inchain)
236 if (q == o + 1) in = true;
237 if (in && refstat >= 1 && refstat <= M) arv = TN(refstat - 1, o);
238 }
239 if (hitclass[o] == r && o < cp->second.hit.size()) {
240 T p = cp->second.hit[o];
241 if (o < cp->second.delayed_hit.size())
242 p = T(p + cp->second.delayed_hit[o]);
243 TNn(ind - 1, r - 1) = T(TNn(ind - 1, r - 1) + arv * p);
244 } else if (o < missclass.size() && missclass[o] == r &&
245 o < cp->second.miss.size()) {
246 TNn(ind - 1, r - 1) = T(TNn(ind - 1, r - 1) + arv * cp->second.miss[o]);
247 }
248 }
249 } else if (detail::sn_is_cache_class(hitclass, missclass, r)) {
250 const T den = detail::sn_chain_refvisits(sn, c, refstat, inchain);
251 if (den > zero)
252 TNn(ind - 1, r - 1) =
253 T(sn.nodevisits[c](ind - 1, r - 1) / den * tot);
254 }
255 }
256 }
257 }
258
259 // pass 2: every station's own throughput
260 for (std::size_t ist = 0; ist < M; ++ist) {
261 const std::size_t ind = sn.station_to_node[ist];
262 if (ind == 0) continue;
263 for (std::size_t r = 0; r < R; ++r) TNn(ind - 1, r) = TN(ist, r);
264 }
265
266 // pass 3: what the node routing carries out of a non-station node
267 if (ANn.rows() != I || sn.rtnodes.rows() != I * R) return TNn;
268 for (std::size_t ind = 1; ind <= I; ++ind) {
269 const qn::NodeType nt = sn.nodes[ind - 1].nodetype;
270 if (nt == qn::NodeType::Sink) continue;
271 if (nt == qn::NodeType::Source) {
272 const std::size_t ist = sn.nodes[ind - 1].station;
273 if (ist != 0)
274 for (std::size_t s = 0; s < R; ++s) TNn(ind - 1, s) = TN(ist - 1, s);
275 continue;
276 }
277 // a Join is accumulated even though it IS a station: the reference's
278 // Join arm has no isstation guard, and a Join's throughput is the rate
279 // its outgoing edges carry, not the rate its (auxiliary) station serves
280 if (nt != qn::NodeType::Join && sn.nodes[ind - 1].station != 0) continue;
281 for (std::size_t c = 0; c < C; ++c) {
282 const std::vector<std::size_t>& inchain = sn.inchain[c];
283 for (std::size_t r : inchain) {
284 bool anystateful = false;
285 if (c < sn.visits.size())
286 for (std::size_t a = 0; a < sn.visits[c].rows(); ++a)
287 if (sn.visits[c](a, r - 1) != zero) anystateful = true;
288 if (!anystateful) continue;
289 for (std::size_t s : inchain)
290 for (std::size_t jnd = 1; jnd <= I; ++jnd) {
291 if (nt == qn::NodeType::Cache && ind == jnd) continue;
292 TNn(ind - 1, s - 1) =
293 T(TNn(ind - 1, s - 1) +
294 ANn(ind - 1, r - 1) *
295 sn.rtnodes((ind - 1) * R + (r - 1), (jnd - 1) * R + (s - 1)));
296 }
297 }
298 }
299 }
300 return TNn;
301}
302
303} // namespace api
304} // namespace line
305
306#endif // LINE_API_SN_SN_NODE_METRICS_H
std::size_t rows() const
Definition matrix.h:89
A network plus its refreshed NetworkStruct.
Matrix< T > sn_get_node_tput_from_tput(const qn::NetworkStruct< T > &sn, const Matrix< T > &TN, const Matrix< T > &ANn, const std::map< std::size_t, CacheActualProb< T > > &cache_prob=std::map< std::size_t, CacheActualProb< T > >())
Port of sn_get_node_tput_from_tput.
Matrix< T > sn_get_node_arvr_from_tput(const qn::NetworkStruct< T > &sn, const Matrix< T > &TN, const Matrix< T > &AN)
Port of sn_get_node_arvr_from_tput.
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:324
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
The converged cache split, per ORIGINAL class of one Cache node.
std::vector< std::size_t > missclass
std::vector< std::size_t > hitclass