LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_validate.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_VALIDATE_H
6#define LINE_API_SN_SN_VALIDATE_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Consistency checks on a NetworkStruct, a port of
12 * jar/src/main/java/jline/api/sn/SnValidate.java and ValidationLevel.java.
13 *
14 * The reference returns a LIST OF MESSAGES rather than throwing, so a caller can
15 * report every defect of a struct at once instead of the first one; that
16 * contract is kept here. The setters in sn_setters.h are the intended callers:
17 * they mutate a struct in place and can leave it internally inconsistent, and
18 * this is what tells them so.
19 *
20 * WHAT DIFFERS FROM THE JAR, and why. The reference tests the SHAPE of the
21 * matrices it stores (rates, scv, nservers, njobs, classprio, phases each being
22 * an independently-sized Matrix that could disagree with nstations/nclasses).
23 * This port holds nservers, the populations, the priorities and the phase counts
24 * as per-station and per-class members of Station and JobClass, so those shapes
25 * cannot disagree by construction and the corresponding checks would be vacuous.
26 * The checks that survive are the ones with real content: the two genuinely
27 * free-standing matrices (rates, scv), the parallel `disabled` mask that
28 * replaces MATLAB's NaN sentinel, and the value checks on rates, populations,
29 * routing and servers.
30 *
31 * NaN IS NOT A DISABLED MARKER HERE. The reference skips a NaN rate because
32 * MATLAB writes NaN for a pair the class never visits; this port carries that
33 * out of band in `disabled` (Rational has no NaN), so the skip reads the flag.
34 *
35 * ARITHMETIC: field plus comparisons, so it instantiates under Rational.
36 */
37
38#include <cmath>
39#include <cstddef>
40#include <string>
41#include <vector>
42
45#include "line/num/number.h"
46
47namespace line {
48namespace api {
49
50/** How much of the struct to check; the reference's ValidationLevel. */
51enum class ValidationLevel {
52 Full, ///< every check
53 Minimal, ///< dimensions only
54 None ///< skip everything
55};
56
57namespace detail {
58
59inline std::string idx2(std::size_t i, std::size_t j) {
60 return "[" + std::to_string(i) + "," + std::to_string(j) + "]";
61}
62
63} // namespace detail
64
65/** The (nstations x nclasses) matrices agree with the declared dimensions. */
66template <class T>
67std::vector<std::string> sn_validate_dimensions(const qn::NetworkStruct<T>& sn) {
68 const std::size_t M = sn.nstations, K = sn.nclasses;
69 std::vector<std::string> errors;
70 const std::string want =
71 " do not match (nstations=" + std::to_string(M) + " x nclasses=" + std::to_string(K) + ")";
72 if (sn.rates.rows() != M || sn.rates.cols() != K)
73 errors.push_back("rates matrix dimensions (" + std::to_string(sn.rates.rows()) + "x" +
74 std::to_string(sn.rates.cols()) + ")" + want);
75 if (sn.scv.rows() != M || sn.scv.cols() != K)
76 errors.push_back("scv matrix dimensions (" + std::to_string(sn.scv.rows()) + "x" +
77 std::to_string(sn.scv.cols()) + ")" + want);
78 if (!sn.disabled.empty()) {
79 if (sn.disabled.size() != M)
80 errors.push_back("disabled has " + std::to_string(sn.disabled.size()) +
81 " rows, expected nstations=" + std::to_string(M));
82 for (std::size_t i = 0; i < sn.disabled.size(); ++i)
83 if (sn.disabled[i].size() != K) {
84 errors.push_back("disabled row " + std::to_string(i) + " has " +
85 std::to_string(sn.disabled[i].size()) +
86 " entries, expected nclasses=" + std::to_string(K));
87 break;
88 }
89 }
90 if (sn.stations.size() != M)
91 errors.push_back("stations list holds " + std::to_string(sn.stations.size()) +
92 " entries, expected nstations=" + std::to_string(M));
93 if (sn.classes.size() != K)
94 errors.push_back("classes list holds " + std::to_string(sn.classes.size()) +
95 " entries, expected nclasses=" + std::to_string(K));
96 return errors;
97}
98
99/** Rates and SCVs are non-negative wherever the pair is enabled. */
100template <class T>
101std::vector<std::string> sn_validate_rates(const qn::NetworkStruct<T>& sn) {
102 std::vector<std::string> errors;
103 const T zero = num_traits<T>::from_int(0);
104 for (std::size_t i = 0; i < sn.rates.rows(); ++i)
105 for (std::size_t j = 0; j < sn.rates.cols(); ++j) {
106 if (i < sn.disabled.size() && j < sn.disabled[i].size() && sn.disabled[i][j]) continue;
107 if (sn.rates(i, j) < zero)
108 errors.push_back("rates" + detail::idx2(i, j) + " = " +
109 std::to_string(num_traits<T>::to_double(sn.rates(i, j))) +
110 " is negative");
111 }
112 for (std::size_t i = 0; i < sn.scv.rows(); ++i)
113 for (std::size_t j = 0; j < sn.scv.cols(); ++j) {
114 if (i < sn.disabled.size() && j < sn.disabled[i].size() && sn.disabled[i][j]) continue;
115 if (sn.scv(i, j) < zero)
116 errors.push_back("scv" + detail::idx2(i, j) + " = " +
117 std::to_string(num_traits<T>::to_double(sn.scv(i, j))) +
118 " is negative (SCV must be >= 0)");
119 }
120 return errors;
121}
122
123/** Class populations are neither NaN nor negative; an open class may be Inf. */
124template <class T>
125std::vector<std::string> sn_validate_population(const qn::NetworkStruct<T>& sn) {
126 std::vector<std::string> errors;
127 for (std::size_t k = 0; k < sn.classes.size(); ++k) {
128 const double n = sn.classes[k].population;
129 if (std::isnan(n))
130 errors.push_back("njobs for class " + std::to_string(k) + " is NaN");
131 else if (std::isfinite(n) && n < 0)
132 errors.push_back("njobs for class " + std::to_string(k) + " = " + std::to_string(n) +
133 " is negative");
134 }
135 return errors;
136}
137
138/** Every non-empty row of rt is a probability vector summing to one. */
139template <class T>
140std::vector<std::string> sn_validate_routing(const qn::NetworkStruct<T>& sn) {
141 std::vector<std::string> errors;
142 const T zero = num_traits<T>::from_int(0);
143 const double tolerance = 1e-6;
144 for (std::size_t i = 0; i < sn.rt.rows(); ++i) {
145 T rowSum = zero;
146 bool hasNonZero = false;
147 for (std::size_t j = 0; j < sn.rt.cols(); ++j) {
148 const T v = sn.rt(i, j);
149 if (v < zero)
150 errors.push_back("rt" + detail::idx2(i, j) + " = " +
151 std::to_string(num_traits<T>::to_double(v)) + " is negative");
152 if (v > zero) hasNonZero = true;
153 rowSum += v;
154 }
155 const double s = num_traits<T>::to_double(rowSum);
156 if (hasNonZero && std::fabs(s - 1.0) > tolerance)
157 errors.push_back("rt row " + std::to_string(i) + " sum = " + std::to_string(s) +
158 " (expected 1.0)");
159 }
160 return errors;
161}
162
163/** Server counts are positive, except at a Source or a Sink. */
164template <class T>
165std::vector<std::string> sn_validate_servers(const qn::NetworkStruct<T>& sn) {
166 std::vector<std::string> errors;
167 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
168 const double n = sn.stations[i].nservers;
169 if (std::isnan(n)) {
170 errors.push_back("nservers[" + std::to_string(i) + "] is NaN");
171 } else if (n <= 0 && !std::isinf(n)) {
172 const qn::NodeType nt = sn.stations[i].nodetype;
173 if (nt != qn::NodeType::Source && nt != qn::NodeType::Sink)
174 errors.push_back("nservers[" + std::to_string(i) + "] = " + std::to_string(n) +
175 " must be positive");
176 }
177 }
178 return errors;
179}
180
181/**
182 * @brief Consistency checks on a NetworkStruct, a port of
183 * jar/src/main/java/jline/api/sn/SnValidate.java and
184 * ValidationLevel.java.
185 *
186 * @param sn the struct to check
187 * @param level how much of it to check
188 * @return one message per defect, empty when the struct is consistent
189 */
190template <class T>
191std::vector<std::string> sn_validate(const qn::NetworkStruct<T>& sn,
193 std::vector<std::string> errors;
194 if (level == ValidationLevel::None) return errors;
195 const std::vector<std::string> dims = sn_validate_dimensions(sn);
196 errors.insert(errors.end(), dims.begin(), dims.end());
197 if (level != ValidationLevel::Full) return errors;
198 const std::vector<std::string> r = sn_validate_rates(sn);
199 errors.insert(errors.end(), r.begin(), r.end());
200 const std::vector<std::string> p = sn_validate_population(sn);
201 errors.insert(errors.end(), p.begin(), p.end());
202 const std::vector<std::string> rt = sn_validate_routing(sn);
203 errors.insert(errors.end(), rt.begin(), rt.end());
204 const std::vector<std::string> ns = sn_validate_servers(sn);
205 errors.insert(errors.end(), ns.begin(), ns.end());
206 return errors;
207}
208
209// ---- index guards, returned as a message or the empty string ------------
210
211template <class T>
212std::string sn_validate_station_index(const qn::NetworkStruct<T>& sn, std::size_t ist,
213 const std::string& paramName = "stationIdx") {
214 if (ist >= sn.nstations)
215 return paramName + "=" + std::to_string(ist) + " is out of bounds [0, " +
216 std::to_string(sn.nstations == 0 ? 0 : sn.nstations - 1) + "]";
217 return std::string();
218}
219
220template <class T>
221std::string sn_validate_class_index(const qn::NetworkStruct<T>& sn, std::size_t r,
222 const std::string& paramName = "classIdx") {
223 if (r >= sn.nclasses)
224 return paramName + "=" + std::to_string(r) + " is out of bounds [0, " +
225 std::to_string(sn.nclasses == 0 ? 0 : sn.nclasses - 1) + "]";
226 return std::string();
227}
228
229template <class T>
230std::string sn_validate_node_index(const qn::NetworkStruct<T>& sn, std::size_t ind,
231 const std::string& paramName = "nodeIdx") {
232 const std::size_t n = sn.nodes.size();
233 if (ind >= n)
234 return paramName + "=" + std::to_string(ind) + " is out of bounds [0, " +
235 std::to_string(n == 0 ? 0 : n - 1) + "]";
236 return std::string();
237}
238
239template <class T>
240std::string sn_validate_node_type(const qn::NetworkStruct<T>& sn, std::size_t ind,
241 qn::NodeType expected) {
242 if (ind >= sn.nodes.size())
243 return "nodeIdx=" + std::to_string(ind) + " is out of bounds";
244 const qn::NodeType actual = sn.nodes[ind].nodetype;
245 if (actual != expected)
246 return "Node " + std::to_string(ind) + " is " + lang::node_type_to_text(actual) +
247 ", expected " + lang::node_type_to_text(expected);
248 return std::string();
249}
250
251} // namespace api
252} // namespace line
253
254#endif // LINE_API_SN_SN_VALIDATE_H
A network plus its refreshed NetworkStruct.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
std::vector< std::string > sn_validate_population(const qn::NetworkStruct< T > &sn)
Class populations are neither NaN nor negative; an open class may be Inf.
std::string sn_validate_node_type(const qn::NetworkStruct< T > &sn, std::size_t ind, qn::NodeType expected)
std::vector< std::string > sn_validate(const qn::NetworkStruct< T > &sn, ValidationLevel level=ValidationLevel::Full)
Consistency checks on a NetworkStruct, a port of jar/src/main/java/jline/api/sn/SnValidate....
std::string sn_validate_class_index(const qn::NetworkStruct< T > &sn, std::size_t r, const std::string &paramName="classIdx")
std::vector< std::string > sn_validate_routing(const qn::NetworkStruct< T > &sn)
Every non-empty row of rt is a probability vector summing to one.
std::vector< std::string > sn_validate_rates(const qn::NetworkStruct< T > &sn)
Rates and SCVs are non-negative wherever the pair is enabled.
std::string sn_validate_station_index(const qn::NetworkStruct< T > &sn, std::size_t ist, const std::string &paramName="stationIdx")
std::vector< std::string > sn_validate_dimensions(const qn::NetworkStruct< T > &sn)
The (nstations x nclasses) matrices agree with the declared dimensions.
Definition sn_validate.h:67
ValidationLevel
How much of the struct to check; the reference's ValidationLevel.
Definition sn_validate.h:51
@ Minimal
dimensions only
Definition sn_validate.h:53
@ None
skip everything
Definition sn_validate.h:54
std::vector< std::string > sn_validate_servers(const qn::NetworkStruct< T > &sn)
Server counts are positive, except at a Source or a Sink.
std::string sn_validate_node_index(const qn::NetworkStruct< T > &sn, std::size_t ind, const std::string &paramName="nodeIdx")
const char * node_type_to_text(NodeType t)
Name of a node kind, for diagnostics.
Definition lang_types.h:341
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.