LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_validate_contract.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_CONTRACT_H
6#define LINE_API_SN_SN_VALIDATE_CONTRACT_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Check a NetworkStruct against its contract (sn_contract.h): the C++ twin of
12 * MATLAB's sn_validate. (`sn_validate` itself is taken in this port by the
13 * JAR's SnValidate value checks, see sn_validate.h.)
14 *
15 * Four kinds of violation are reported, one string each: a required field is
16 * missing, a field is not in the contract at all, a value has a kind the
17 * contract does not admit, and a non-empty value has a shape none of the
18 * field's shapes admits.
19 *
20 * The check runs on the FIELD VIEW of the struct (sn_contract_view), so a test
21 * can drop or inject a field as the MATLAB test does with rmfield. C++ has no
22 * reflection, so whether every DATA MEMBER of NetworkStruct is declared in the
23 * contract is checked when the view is generated, by
24 * `doc/generate-sn-contract.py --check`; at run time an unknown field is one in
25 * the view the contract does not list. qn::Network::get_struct runs it once per
26 * rebuild at VerboseLevel::DEBUG or with LINE_VALIDATE_SN=1, and warns.
27 */
28
29#include <cmath>
30#include <cstdlib>
31#include <regex>
32#include <set>
33#include <sstream>
34#include <string>
35#include <vector>
36
39#include "line/util/error.h"
41
42namespace line {
43namespace api {
44
45namespace detail {
46
47inline std::vector<std::string> contract_split(const std::string& s, char sep) {
48 std::vector<std::string> out;
49 std::string cur;
50 for (char ch : s) {
51 if (ch == sep) {
52 out.push_back(cur);
53 cur.clear();
54 } else {
55 cur += ch;
56 }
57 }
58 out.push_back(cur);
59 return out;
60}
61
62inline std::string contract_join(const std::vector<std::string>& v, const std::string& sep) {
63 std::string out;
64 for (std::size_t i = 0; i < v.size(); ++i) {
65 out += (i ? sep : "") + v[i];
66 }
67 return out;
68}
69
70/** '*' admits any extent; a trailing '?' makes the extent an upper bound. */
71inline bool contract_extent_admits(std::string tok, long n, const std::map<char, double>& dims) {
72 if (n < 0) {
73 return false;
74 }
75 bool upper = !tok.empty() && tok.back() == '?';
76 if (upper) {
77 tok.pop_back();
78 }
79 if (tok == "*") {
80 return true;
81 }
82 static const std::regex term_re("^([0-9]*)([A-Z]*)$");
83 double total = 0;
84 for (const auto& term : contract_split(tok, '+')) {
85 std::smatch m;
86 if (!std::regex_match(term, m, term_re) || (m[1].length() == 0 && m[2].length() == 0)) {
87 return false;
88 }
89 double f = m[1].length() ? std::stod(m[1].str()) : 1.0;
90 for (char ch : m[2].str()) {
91 auto it = dims.find(ch);
92 f *= (it == dims.end()) ? std::nan("") : it->second;
93 }
94 total += f;
95 }
96 return upper ? n <= total : static_cast<double>(n) == total;
97}
98
99inline bool contract_shape_admits(const SnFieldView& v, const std::string& shape,
100 const std::map<char, double>& dims) {
101 auto parts = contract_split(shape, 'x');
102 if (parts.size() == 1) {
103 return contract_extent_admits(parts[0], sn_view_extent(v), dims);
104 }
105 if (parts.size() != 2) {
106 return false;
107 }
108 if (v.ndim == 2) {
109 return contract_extent_admits(parts[0], v.rows, dims) && contract_extent_admits(parts[1], v.cols, dims);
110 }
111 if (v.ndim != 1 || !contract_extent_admits(parts[0], v.rows, dims)) {
112 return false;
113 }
114 for (long e : v.elems) {
115 if (e != kEmptyExtent && !contract_extent_admits(parts[1], e, dims)) {
116 return false;
117 }
118 }
119 return true;
120}
121
122inline std::string contract_describe(const SnFieldView& v) {
123 if (v.ndim == 2) {
124 return "[" + std::to_string(v.rows) + " " + std::to_string(v.cols) + "]";
125 }
126 if (v.ndim == 1) {
127 std::set<long> inner(v.elems.begin(), v.elems.end());
128 std::vector<std::string> s;
129 for (long e : inner) {
130 s.push_back(e == kEmptyExtent ? "empty" : e == kNoExtent ? "none" : std::to_string(e));
131 }
132 return "of size " + std::to_string(v.rows) + " (element extents " + contract_join(s, ",") + ")";
133 }
134 return "a " + v.kind;
135}
136
137} // namespace detail
138
139/** True when get_struct should validate: debug verbosity, or LINE_VALIDATE_SN=1. */
141 const char* env = std::getenv("LINE_VALIDATE_SN");
142 return util::LineConsole::get_verbose() == util::VerboseLevel::DEBUG || (env && std::string(env) == "1");
143}
144
145/**
146 * The contract violations of a field view; with `error` true, throws
147 * InternalError with id LINE:internal:Contract when there are any.
148 */
149inline std::vector<std::string> sn_validate_contract(const SnFieldViews& views, bool error = false) {
150 std::vector<std::string> v;
151 std::map<std::string, const SnContractField*> by_name;
152 for (const auto& f : sn_contract()) {
153 by_name[f.name] = &f;
154 if (std::string(f.group) == "required" && std::string(f.via) != "" && !views.fields.count(f.name)) {
155 v.push_back(std::string("required field sn.") + f.name + " is missing");
156 }
157 }
158 for (const auto& kv : views.fields) {
159 const std::string& name = kv.first;
160 const SnFieldView& fv = kv.second;
161 auto it = by_name.find(name);
162 if (it == by_name.end() || std::string(it->second->via).empty()) {
163 v.push_back("sn." + name + " is not in sn_contract: misspelled, or a new field that was never declared");
164 continue;
165 }
166 const SnContractField& c = *it->second;
167 auto kinds = detail::contract_split(c.kinds, ',');
168 bool kind_ok = std::string(c.kinds).empty();
169 for (const auto& k : kinds) {
170 kind_ok = kind_ok || k == fv.kind;
171 }
172 if (!kind_ok) {
173 v.push_back("sn." + name + " is a " + fv.kind + ", but the contract admits " +
174 detail::contract_join(kinds, " or "));
175 continue;
176 }
177 if (std::string(c.shapes).empty() || fv.empty) {
178 continue;
179 }
180 auto shapes = detail::contract_split(c.shapes, ',');
181 bool ok = false;
182 for (const auto& s : shapes) {
183 ok = ok || detail::contract_shape_admits(fv, s, views.dims);
184 }
185 if (!ok) {
186 v.push_back("sn." + name + " is " + detail::contract_describe(fv) + ", but the contract admits " +
187 detail::contract_join(shapes, " or "));
188 }
189 }
190 if (error && !v.empty()) {
191 throw InternalError(ErrorKind{"Contract"}, "NetworkStruct violates its contract:\n " +
192 detail::contract_join(v, "\n "));
193 }
194 return v;
195}
196
197/** The contract violations of a struct; see sn_validate_contract(const SnFieldViews&, bool). */
198template <class T>
199std::vector<std::string> sn_validate_contract(const qn::NetworkStruct<T>& sn, bool error = false) {
201}
202
203} // namespace api
204} // namespace line
205
206#endif // LINE_API_SN_SN_VALIDATE_CONTRACT_H
InternalError(const std::string &what)
Definition error.h:133
A network plus its refreshed NetworkStruct.
static VerboseLevel get_verbose()
The session verbosity.
The exception types the port throws.
Running progress log of a LINE solver run (the "solver console").
std::vector< std::string > sn_validate_contract(const SnFieldViews &views, bool error=false)
The contract violations of a field view; with error true, throws InternalError with id LINE:internal:...
SnFieldViews sn_contract_view(const qn::NetworkStruct< T > &sn)
The field view of a struct: one entry per contract field this port stores, keyed by the MATLAB name,...
bool sn_validate_requested()
True when get_struct should validate: debug verbosity, or LINE_VALIDATE_SN=1.
constexpr long kEmptyExtent
long sn_view_extent(const SnFieldView &v)
The 1-D extent of a field view: a container's size, a vector Matrix's length.
constexpr long kNoExtent
const std::array< SnContractField, 127 > & sn_contract()
Definition sn_contract.h:44
std::string upper(std::string s)
Definition srvn_writer.h:86
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The NetworkStruct contract: every field a NetworkStruct may carry, keyed by its MATLAB name,...
The FIELD VIEW the NetworkStruct contract is checked on: for each value, its kind (Matrix,...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
One NetworkStruct field; kinds and shapes are comma-separated.
Definition sn_contract.h:34
const char * shapes
admissible shapes of a non-empty value
Definition sn_contract.h:40
const char * kinds
admissible sn_field_kind() values
Definition sn_contract.h:39
The view of one field.
The view of a whole struct: its dimensions and one view per contract field, by MATLAB name.
std::map< char, double > dims
std::map< std::string, SnFieldView > fields