LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_node_tables.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_SOLVERS_SOLVER_NODE_TABLES_H
6#define LINE_SOLVERS_SOLVER_NODE_TABLES_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The NODE-indexed view of a station result, behind `getAvgNodeTable`.
12 *
13 * ONE DEFINITION, THREE CALLERS. `line-cli -a node` and `-a nodechain` were the
14 * first two, and the comment on `node_metrics` already said why they must not
15 * each build the scatter: "recomputing them in the second arm would be a second
16 * definition of the same scatter, free to disagree with the first". The third
17 * is the example corpus. Four `cache_replc_*` references call
18 * `getAvgNodeTable()` rather than `getAvgTable()` -- a cache model's answer
19 * lives at the Cache and the ClassSwitch, neither of which is a station -- and
20 * their C++ twins printed the station table instead, so every Cache and
21 * ClassSwitch row was absent and the goldens' 71 cells went unmeasured. This
22 * header is what lets a twin print the table its reference printed without a
23 * fourth copy of the rule.
24 *
25 * ARITHMETIC: field. Sums, quotients and the two `sn_*` ports it delegates to.
26 */
27
28#include <cstddef>
29#include <map>
30#include <string>
31#include <vector>
32
35#include "line/num/number.h"
38#include "line/util/matrix.h"
39
40namespace line {
41namespace solvers {
42
43/**
44 * The station AvgResult of a solver whose runner returns its own solution type,
45 * i.e. SSA and Fluid: their QN/UN/RN/TN are the same six columns the AvgTable
46 * arm already prints, so a VIEW of them is a view of the same numbers.
47 *
48 * The two columns those runners do not carry are filled the way the reference
49 * fills them, and the SAME way their own `-a avg` arms do:
50 * - ResidT is `sn_get_residt_from_respt`, the per-JOB residence time. It is a
51 * pure function of `sn` and RN, which is why SolverMVA's runner calls it
52 * rather than deriving it inside the analyzer, and why a solver that reports
53 * no residence time of its own is not thereby excused from reporting one.
54 * ResidT = RespT holds only when every station is visited once per cycle;
55 * assuming it cost `sdroute_closed` a factor of 3 on the two Queues and
56 * `init_state_ps` a factor of 17 on Queue1.
57 * - ArvR is the throughput except at a Source, which has no arrivals TO ITSELF.
58 * Restating either rule here rather than sharing it would let `-a avg` and
59 * `-a node` disagree on one column of one model, which is the divergence
60 * `-s mva` vs `-s fluid` already produced once on gallery_mm1.
61 */
62template <class T>
64 const line::Matrix<double>& QN,
65 const line::Matrix<double>& UN,
66 const line::Matrix<double>& RN,
67 const line::Matrix<double>& TN,
68 const std::vector<double>& CN,
69 const std::vector<double>& XN,
70 const std::string& method) {
71 const std::size_t M = sn.nstations, R = sn.nclasses;
72 const T zero = line::num_traits<T>::from_int(0);
74 r.QN = line::Matrix<T>(M, R, zero);
75 r.UN = line::Matrix<T>(M, R, zero);
76 r.RN = line::Matrix<T>(M, R, zero);
77 r.WN = line::Matrix<T>(M, R, zero);
78 r.AN = line::Matrix<T>(M, R, zero);
79 r.TN = line::Matrix<T>(M, R, zero);
80 std::vector<std::vector<bool>> srcmask(M, std::vector<bool>(R, false));
81 for (std::size_t i = 0; i < M; ++i) {
82 if (sn.stations[i].sched == line::lang::SchedStrategy::EXT)
83 for (std::size_t c = 0; c < R; ++c) srcmask[i][c] = true;
84 for (std::size_t c = 0; c < R; ++c) {
85 r.QN(i, c) = line::num_traits<T>::from_double(QN(i, c));
86 r.UN(i, c) = line::num_traits<T>::from_double(UN(i, c));
87 r.RN(i, c) = line::num_traits<T>::from_double(RN(i, c));
88 r.TN(i, c) = line::num_traits<T>::from_double(TN(i, c));
89 }
90 }
92 // ARRIVAL RATE IS AN INFLOW, NOT A THROUGHPUT. `AN = TN` holds only where
93 // the station's own flow balances, and a fluid fixed point need not: on
94 // cache_replc_routing MATLAB's rmf reports Tput 0 at Delay1 and Delay2 while
95 // 0.4 and 0.6 arrive there, so the shortcut dropped those four rows out of
96 // the node table entirely. `SolverFLD/runAnalyzer.m:372` and
97 // `SolverSSA/runAnalyzer.m:199` both derive AN through sn_get_arvr_from_tput
98 // from the class-expanded routing, exactly as CTMC, NC, MAM, BA and QNS
99 // already do in this port; the Source mask is `getAvg`'s own zeroMask.
102 for (std::size_t c = 0; c < CN.size(); ++c)
103 r.CN.push_back(line::num_traits<T>::from_double(CN[c]));
104 for (std::size_t c = 0; c < XN.size(); ++c)
105 r.XN.push_back(line::num_traits<T>::from_double(XN[c]));
106 r.method = method;
107 r.actualmethod = method;
108 return r;
109}
110
111/**
112 * The station table scattered to the NODE index space, plus the two flow
113 * columns the reference recomputes there.
114 *
115 * Factored out of `-a node` because `-a nodechain` aggregates exactly these six
116 * matrices by chain: recomputing them in the second arm would be a second
117 * definition of the same scatter, free to disagree with the first.
118 */
119template <class T>
121 line::Matrix<T> QN, UN, RN, WN, AN, TN; ///< (nnodes x nclasses)
122};
123
124template <class T>
126 const line::mva::AvgResult<T>& r) {
127 const std::size_t I = sn.nodes.size(), R = sn.nclasses;
128 const T zero = line::num_traits<T>::from_int(0);
130 m.QN = line::Matrix<T>(I, R, zero);
131 m.UN = line::Matrix<T>(I, R, zero);
132 m.RN = line::Matrix<T>(I, R, zero);
133 m.WN = line::Matrix<T>(I, R, zero);
134 for (std::size_t ist = 0; ist < sn.nstations; ++ist) {
135 const std::size_t ind = sn.station_to_node[ist];
136 if (!ind) continue;
137 for (std::size_t c = 0; c < R; ++c) {
138 m.QN(ind - 1, c) = r.QN(ist, c);
139 m.UN(ind - 1, c) = r.UN(ist, c);
140 m.RN(ind - 1, c) = r.RN(ist, c);
141 m.WN(ind - 1, c) = r.WN(ist, c);
142 }
143 }
144 // THE TWO RECOMPUTED COLUMNS READ THE REFRESHED STRUCT, exactly as the
145 // station table already does (`solver_mva_runner.h`, `refL`). A cache's
146 // hit/miss split is a SOLVER RESULT: `link()` leaves the self-switch at the
147 // offered 1/2-1/2, and both of these columns are visit-weighted, so reading
148 // the base struct here reports the split nobody computed. Measured on
149 // cache_replc_routing, Cache/Router/Sink hit and miss came out 1/1 against a
150 // golden of 0.8/1.2, and on cache_replc_fifo the Cache read-class ArvR came
151 // out 2 against 1 -- the two halves of one guess, summed.
152 //
153 // NULL for every model with no cache branch, which is the base struct and
154 // the behaviour every other model already had.
156 // AND THE SPLIT ITSELF IS PASSED, not left to be inferred from the visits.
157 // A refreshed struct is only ONE of the two ways a solver states what it
158 // measured, and the arms that do not build one -- CTMC, SSA, and the
159 // Source-Cache-Sink branch of MVA and NC -- still report a hit probability
160 // in `AvgResult::cache`. Without it every one of them fell through to the
161 // visit-ratio branch of sn_get_node_tput_from_tput, i.e. to the 1/2-1/2
162 // `link()` offers before anything is solved: 0.5/0.5 against a golden of
163 // 0.4/0.6 on cache_replc_fifo and cache_replc_lru, 1/1 against 1.1127/
164 // 0.8873 on cache_replc_rr. Empty on every model with no Cache node.
165 std::map<std::size_t, line::api::CacheActualProb<T>> cacheprob;
166 for (std::size_t ci = 0; ci < r.cache.caches.size(); ++ci) {
167 const line::solvers::CacheNodeMetrics<T>& cm = r.cache.caches[ci];
168 if (cm.node == 0 || cm.hitprob.empty()) continue;
170 p.hit = cm.hitprob;
171 p.miss = cm.missprob;
173 cacheprob[cm.node] = p;
174 }
176 m.TN = line::api::sn_get_node_tput_from_tput(refsn, r.TN, m.AN, cacheprob);
177 // WHAT A CACHE SENDS ON IS WHAT THE NEXT NODE RECEIVES, and the visit ratios
178 // cannot say so: `sn.nodevisits` still carries the 1/2-1/2 `link()` offered,
179 // so a ClassSwitch or a Sink downstream of a Cache reported that guess as its
180 // hit and miss ARRIVAL rate even once the departure rate above it was right.
181 // `getAvgNode.m` lines 73-92 close the same gap with the same assignment, and
182 // AFTER both tables are formed rather than before: the fix is to ANn alone
183 // and must not feed back into the TNn that produced it.
184 for (std::size_t cind = 1; cind <= I; ++cind) {
185 if (refsn.nodes[cind - 1].nodetype != line::qn::NodeType::Cache) continue;
186 typename std::map<std::size_t, line::qn::CacheParam<T>>::const_iterator np =
187 refsn.nodeparam.find(cind);
188 if (np == refsn.nodeparam.end()) continue;
189 for (std::size_t ind = 1; ind <= I; ++ind) {
190 const line::qn::NodeType nt = refsn.nodes[ind - 1].nodetype;
191 if (nt != line::qn::NodeType::ClassSwitch && nt != line::qn::NodeType::Sink) continue;
192 for (std::size_t rr = 1; rr <= R; ++rr)
193 if (line::api::detail::sn_is_cache_class(np->second.hitclass,
194 np->second.missclass, rr))
195 m.AN(ind - 1, rr - 1) = m.TN(cind - 1, rr - 1);
196 }
197 }
198 return m;
199}
200} // namespace solvers
201} // namespace line
202
203#endif // LINE_SOLVERS_SOLVER_NODE_TABLES_H
What a solver observed about the Cache nodes of a model.
A network plus its refreshed NetworkStruct.
std::map< std::size_t, CacheParam< T > > nodeparam
Cache parameters by 1-based NODE index; only Cache nodes have an entry.
std::vector< NodeDef > nodes
every node, in creation order
Dense matrix and non-owning view.
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
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
Matrix< T > filter_metric(const qn::NetworkStruct< T > &L, const Matrix< T > &metric, MetricKind kind, const std::vector< std::vector< bool > > *zero_mask)
Port of filterMetric: what @@NetworkSolver/getAvg does between the analyzer and the caller.
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
line::mva::AvgResult< T > avg_result_from_sim(const line::qn::NetworkStruct< T > &sn, const line::Matrix< double > &QN, const line::Matrix< double > &UN, const line::Matrix< double > &RN, const line::Matrix< double > &TN, const std::vector< double > &CN, const std::vector< double > &XN, const std::string &method)
The station AvgResult of a solver whose runner returns its own solution type, i.e.
NodeMetrics< T > node_metrics(const line::qn::NetworkStruct< T > &sn, const line::mva::AvgResult< T > &r)
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Ports of matlab/src/api/sn/sn_get_node_arvr_from_tput.m and sn_get_node_tput_from_tput....
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
The converged cache split, per ORIGINAL class of one Cache node.
The metrics getAvg returns, after filtering.
std::shared_ptr< qn::NetworkStruct< T > > refreshed_struct
The struct whose cache self-switch carries the CONVERGED hit/miss split, filled by the cacheqn branch...
Matrix< T > TN
throughput
Matrix< T > RN
response time, per visit
Matrix< T > UN
utilization
Matrix< T > WN
residence time, per job
std::string method
the method asked for
std::string actualmethod
the algorithm that ran
Matrix< T > QN
queue length
std::vector< T > CN
system response time per class
std::vector< T > XN
system throughput per class
solvers::CacheMetrics< T > cache
What the cache branches observed, EMPTY on a model with no Cache node and on every solver that does n...
Matrix< T > AN
arrival rate
One Cache node's measured behaviour.
std::vector< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
std::vector< T > delayedprob
(K) delayed-hit fraction, EMPTY off a retrieval system
std::size_t node
1-based node index of the Cache
The station table scattered to the NODE index space, plus the two flow columns the reference recomput...
line::Matrix< T > TN
(nnodes x nclasses)