LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
srvn_writer.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_LANG_LQN_SRVN_WRITER_H
6#define LINE_LANG_LQN_SRVN_WRITER_H
7
8/**
9 * @file
10 * @ingroup line_lang
11 * SRVN output (the LQNS `.in` text grammar) for layered models, the C++ twin of
12 * `lqnx2srvn` / `srvn_save` in python/line_solver/io/srvn_io.py.
13 *
14 * The writer is the reader's inverse: an LQN XML document goes to SRVN text, so
15 * `write_srvn` is `write_lqnx` followed by `lqnx2srvn`, and the mapping of a
16 * model onto the LQN schema stays in one place. The output is deterministic and
17 * byte-identical across the four codebases (`srvn/expected_srvn` golden in
18 * line-test.git). What SRVN cannot say is refused by name
19 * (`LINE:unsupported:Format`) rather than dropped: an LQX program, SPEX
20 * parameters, a semaphore's initial state, the LINE dialect elements (cache,
21 * setup, call groups), and names that are not LQNS identifiers. Requests for
22 * output (results, histograms, mva-info) are skipped, as the reader skips them.
23 * Entry phases carry no activity names in SRVN, so a phase activity reads back
24 * as `<entry>_<phase>`.
25 *
26 * The XML is read with `xml::parse`, then brought to what Python's ElementTree
27 * (expat) hands the reference: attribute whitespace normalized to spaces and
28 * numeric character references expanded. Malformed XML is `LINE:input:Format`;
29 * its message is this parser's, not expat's.
30 */
31
32#include <cmath>
33#include <cstdio>
34#include <cstdlib>
35#include <fstream>
36#include <map>
37#include <memory>
38#include <set>
39#include <string>
40#include <utility>
41#include <vector>
42
45#include "line/util/error.h"
46#include "line/util/xml.h"
47
48namespace line {
49namespace lqn {
50
51namespace srvn_out {
52
53using xml::Element;
54
55inline UnsupportedError unsup(const std::string& msg) {
56 return UnsupportedError(ErrorKind{"Format"}, "SRVN output: " + msg);
57}
58
59inline InputError xerr(const std::string& msg) { return InputError(ErrorKind{"Format"}, "SRVN output: " + msg); }
60
61/** `el.get(key)`, with Python's None as a null pointer. */
62inline const std::string* get(const Element* el, const std::string& key) {
63 for (const auto& kv : el->attrs)
64 if (kv.first == key) return &kv.second;
65 return NULL;
66}
67
68/** `el.get(key, dflt)`. */
69inline std::string get(const Element* el, const std::string& key, const std::string& dflt) {
70 const std::string* v = get(el, key);
71 return v ? *v : dflt;
72}
73
74/** `'%s' % el.get(key)`: None prints as "None". */
75inline std::string show(const Element* el, const std::string& key) {
76 const std::string* v = get(el, key);
77 return v ? *v : std::string("None");
78}
79
80inline std::string lower(std::string s) {
81 for (char& c : s)
82 if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
83 return s;
84}
85
86inline std::string upper(std::string s) {
87 for (char& c : s)
88 if (c >= 'a' && c <= 'z') c = static_cast<char>(c - 'a' + 'A');
89 return s;
90}
91
92inline bool starts_with(const std::string& s, const char* p) { return s.compare(0, std::string(p).size(), p) == 0; }
93
94inline void utf8(std::string& out, unsigned long cp) {
95 if (cp < 0x80) out += static_cast<char>(cp);
96 else if (cp < 0x800) {
97 out += static_cast<char>(0xC0 | (cp >> 6));
98 out += static_cast<char>(0x80 | (cp & 0x3F));
99 } else if (cp < 0x10000) {
100 out += static_cast<char>(0xE0 | (cp >> 12));
101 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
102 out += static_cast<char>(0x80 | (cp & 0x3F));
103 } else {
104 out += static_cast<char>(0xF0 | (cp >> 18));
105 out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
106 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
107 out += static_cast<char>(0x80 | (cp & 0x3F));
108 }
109}
110
111/**
112 * An attribute value as expat delivers it: literal tab, CR and LF become spaces
113 * (a CR LF pair one space), and `&#n;` / `&#xh;` are expanded afterwards.
114 */
115inline std::string normalize_attr(const std::string& v) {
116 std::string ws;
117 ws.reserve(v.size());
118 for (std::size_t i = 0; i < v.size(); ++i) {
119 const char c = v[i];
120 if (c == '\r') {
121 ws += ' ';
122 if (i + 1 < v.size() && v[i + 1] == '\n') ++i;
123 } else if (c == '\n' || c == '\t') {
124 ws += ' ';
125 } else {
126 ws += c;
127 }
128 }
129 std::string out;
130 out.reserve(ws.size());
131 for (std::size_t i = 0; i < ws.size(); ++i) {
132 if (ws[i] == '&' && i + 2 < ws.size() && ws[i + 1] == '#') {
133 const std::size_t semi = ws.find(';', i);
134 if (semi != std::string::npos) {
135 const bool hex = ws[i + 2] == 'x';
136 const std::string digits = ws.substr(i + (hex ? 3 : 2), semi - i - (hex ? 3 : 2));
137 bool ok = !digits.empty();
138 for (char d : digits)
139 ok = ok && (hex ? std::isxdigit(static_cast<unsigned char>(d)) != 0 : srvn_detail::is_digit(d));
140 if (ok) {
141 utf8(out, std::strtoul(digits.c_str(), NULL, hex ? 16 : 10));
142 i = semi;
143 continue;
144 }
145 }
146 }
147 out += ws[i];
148 }
149 return out;
150}
151
152inline void normalize_tree(Element* el) {
153 for (auto& kv : el->attrs) kv.second = normalize_attr(kv.second);
154 for (auto& c : el->children) normalize_tree(c.get());
155}
156
157/** Output requests: read and dropped, as the reader drops H, R and observations. */
158inline bool is_output_tag(const std::string& tag) {
159 return starts_with(tag, "result-") || tag == "service-time-distribution" || tag == "mva-info" ||
160 tag == "convergence-collection";
161}
162
163/** Model children of `el`: results and output requests are skipped. */
164inline std::vector<const Element*> kids(const Element* el) {
165 std::vector<const Element*> out;
166 for (const auto& c : el->children)
167 if (!is_output_tag(c->name)) out.push_back(c.get());
168 return out;
169}
170
171/** `el.find(tag)`: the first direct child with that tag, or null. */
172inline const Element* find(const Element* el, const std::string& tag) {
173 for (const auto& c : el->children)
174 if (c->name == tag) return c.get();
175 return NULL;
176}
177
178inline std::vector<const Element*> findall(const Element* el, const std::string& tag) {
179 return el->child_tags(tag);
180}
181
182inline bool in(const std::string& s, std::initializer_list<const char*> set) {
183 for (const char* x : set)
184 if (s == x) return true;
185 return false;
186}
187
188inline void check(const Element* el, std::initializer_list<const char*> allowed,
189 std::initializer_list<const char*> kidtags = {}) {
190 const std::string* nm = get(el, "name");
191 const std::string where = (nm && !nm->empty()) ? " name=\"" + *nm + "\"" : std::string();
192 for (const auto& kv : el->attrs)
193 if (!in(kv.first, allowed))
194 throw unsup("attribute \"" + kv.first + "\" of <" + el->name + where + "> has no SRVN spelling");
195 for (const Element* k : kids(el))
196 if (!in(k->name, kidtags))
197 throw unsup("<" + k->name + "> inside <" + el->name + where + "> has no SRVN spelling");
198}
199
200/** Python's `float(text)`: surrounding whitespace, underscores between digits, inf/nan. */
201inline bool py_float(const std::string& text, double& x) {
202 std::size_t a = 0, b = text.size();
203 while (a < b && srvn_detail::is_space(text[a])) ++a;
204 while (b > a && srvn_detail::is_space(text[b - 1])) --b;
205 std::string s = text.substr(a, b - a);
206 std::size_t i = 0;
207 std::string sign;
208 if (i < s.size() && (s[i] == '+' || s[i] == '-')) sign = s[i++];
209 const std::string rest = lower(s.substr(i));
210 if (rest == "inf" || rest == "infinity") {
211 x = sign == "-" ? -HUGE_VAL : HUGE_VAL;
212 return true;
213 }
214 if (rest == "nan") {
215 x = std::nan("");
216 return true;
217 }
218 // digitpart := digit (["_"] digit)*
219 std::string clean = sign;
220 auto digitpart = [&](std::size_t& k) -> bool {
221 if (k >= s.size() || !srvn_detail::is_digit(s[k])) return false;
222 clean += s[k++];
223 while (k < s.size()) {
224 if (srvn_detail::is_digit(s[k])) clean += s[k++];
225 else if (s[k] == '_' && k + 1 < s.size() && srvn_detail::is_digit(s[k + 1])) ++k;
226 else break;
227 }
228 return true;
229 };
230 bool mant = digitpart(i);
231 if (i < s.size() && s[i] == '.') {
232 clean += '.';
233 ++i;
234 const bool frac = digitpart(i);
235 mant = mant || frac;
236 }
237 if (!mant) return false;
238 if (i < s.size() && (s[i] == 'e' || s[i] == 'E')) {
239 clean += 'e';
240 ++i;
241 if (i < s.size() && (s[i] == '+' || s[i] == '-')) clean += s[i++];
242 if (!digitpart(i)) return false;
243 }
244 if (i != s.size()) return false;
245 x = std::strtod(clean.c_str(), NULL);
246 return true;
247}
248
249/** `_num`: the attribute as `format_number(float(text))`, or `dflt` (null = None) when absent. */
250inline const std::string* num(const Element* el, const std::string& key, std::string& buf,
251 const char* dflt = NULL) {
252 const std::string* text = get(el, key);
253 if (!text) {
254 if (!dflt) return NULL;
255 buf = dflt;
256 return &buf;
257 }
258 double x = 0;
259 if (!py_float(*text, x))
260 throw unsup(key + "=\"" + *text + "\" of <" + el->name +
261 "> is not a number (SPEX and LQX expressions have no SRVN spelling here)");
262 if (!std::isfinite(x)) throw unsup(key + "=\"" + *text + "\" of <" + el->name + "> is not finite");
263 buf = format_number(x);
264 return &buf;
265}
266
267/** `_num` with a default, which never returns None. */
268inline std::string numd(const Element* el, const std::string& key, const char* dflt) {
269 std::string buf;
270 return *num(el, key, buf, dflt);
271}
272
273/** `[A-Za-z_][A-Za-z0-9_]+$` under re.match, where `$` also matches before a final newline. */
274inline bool srvn_name_ok(const std::string& nm) {
275 std::size_t n = nm.size();
276 if (n > 0 && nm[n - 1] == '\n') --n;
277 if (n < 2) return false;
278 auto alpha = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; };
279 if (!alpha(nm[0])) return false;
280 for (std::size_t i = 1; i < n; ++i)
281 if (!alpha(nm[i]) && !srvn_detail::is_digit(nm[i])) return false;
282 return true;
283}
284
285inline std::string name(const Element* el, const std::string& key = "name") {
286 const std::string* nm = get(el, key);
287 if (!nm) throw xerr("<" + el->name + "> has no " + key);
288 if (!srvn_name_ok(*nm))
289 throw unsup("the name \"" + *nm +
290 "\" is not an LQNS identifier (a letter or \"_\", then at least one letter, digit or \"_\"); "
291 "write the model with use_abstract_names=True");
292 return *nm;
293}
294
295inline std::string join(const std::vector<std::string>& v, const std::string& sep) {
296 std::string s;
297 for (std::size_t i = 0; i < v.size(); ++i) s += (i ? sep : std::string()) + v[i];
298 return s;
299}
300
301struct CallRow {
302 std::string kind, dst;
303 std::vector<std::string> vals;
304};
305
306/** `acts` maps phase -> <activity>; the E-section lines of one entry's phases. */
307inline void phase_lists(const std::map<int, const Element*>& acts,
308 std::vector<std::pair<std::string, std::vector<std::string>>>& lines,
309 std::vector<CallRow>& calls) {
310 const int K = acts.rbegin()->first;
311 static const char* fields[4][3] = {{"s", "host-demand-mean", "0"},
312 {"c", "host-demand-cvsq", "1"},
313 {"Z", "think-time", "0"},
314 {"M", "max-service-time", "0"}};
315 for (int fi = 0; fi < 4; ++fi) {
316 const std::string key = fields[fi][0];
317 std::vector<std::string> vals;
318 bool any = false;
319 for (int k = 1; k <= K; ++k) {
320 std::map<int, const Element*>::const_iterator it = acts.find(k);
321 std::string buf;
322 const std::string* v = it == acts.end() ? NULL : num(it->second, fields[fi][1], buf);
323 any = any || v;
324 vals.push_back(v ? *v : std::string(fields[fi][2]));
325 }
326 if (key == "s" || any) lines.emplace_back(key, vals);
327 if (key == "c") {
328 std::vector<std::string> det;
329 bool anydet = false;
330 for (int k = 1; k <= K; ++k) {
331 std::map<int, const Element*>::const_iterator it = acts.find(k);
332 const bool d =
333 it != acts.end() && upper(get(it->second, "call-order", "STOCHASTIC")) == "DETERMINISTIC";
334 anydet = anydet || d;
335 det.push_back(d ? "1" : "0");
336 }
337 if (anydet) lines.emplace_back("f", det);
338 }
339 }
340 for (int k = 1; k <= K; ++k) {
341 std::map<int, const Element*>::const_iterator it = acts.find(k);
342 if (it == acts.end()) continue;
343 std::set<std::pair<std::string, std::string>> seen;
344 for (const Element* c : kids(it->second)) {
345 if (c->name != "synch-call" && c->name != "asynch-call") continue;
346 const std::pair<std::string, std::string> key(c->name == "synch-call" ? "y" : "z", name(c, "dest"));
347 if (seen.count(key))
348 throw unsup("phase " + std::to_string(k) + " of entry \"" + show(it->second, "name") + "\" calls \"" +
349 key.second + "\" twice; SRVN carries one count per phase");
350 seen.insert(key);
351 CallRow* row = NULL;
352 for (CallRow& r : calls)
353 if (r.kind == key.first && r.dst == key.second) row = &r;
354 if (!row) {
355 calls.push_back(CallRow{key.first, key.second, std::vector<std::string>(K, "0")});
356 row = &calls.back();
357 }
358 row->vals[k - 1] = numd(c, "calls-mean", "0");
359 }
360 }
361}
362
363inline const char* proc_letter(const std::string& s) {
364 static const std::map<std::string, const char*> t = {{"fcfs", "f"}, {"hol", "h"}, {"pri", "p"}, {"pp", "p"},
365 {"rand", "r"}, {"siro", "r"}, {"inf", "i"}, {"cfs", "c"},
366 {"gps", "c"}, {"ps", "s"}};
367 std::map<std::string, const char*>::const_iterator it = t.find(s);
368 return it == t.end() ? NULL : it->second;
369}
370
371inline const char* task_letter(const std::string& s) {
372 static const std::map<std::string, const char*> t = {
373 {"ref", "r"}, {"fcfs", "n"}, {"hol", "h"}, {"pri", "p"}, {"pp", "p"}, {"inf", "i"},
374 {"poll", "P"}, {"burst", "b"}, {"uniform", "u"}, {"semaphore", "S"}, {"rwlock", "W"}};
375 std::map<std::string, const char*>::const_iterator it = t.find(s);
376 return it == t.end() ? NULL : it->second;
377}
378
379inline const char* rwlock_letter(const std::string& s) {
380 if (s == "r-lock") return "R";
381 if (s == "r-unlock") return "U";
382 if (s == "w-lock") return "W";
383 if (s == "w-unlock") return "X";
384 return NULL;
385}
386
387inline const char* sem_letter(const std::string& s) {
388 if (s == "signal") return "P";
389 if (s == "wait") return "V";
390 return NULL;
391}
392
393#define LINE_SRVN_ACT_ATTRS "name", "phase", "host-demand-mean", "host-demand-cvsq", "call-order", "think-time", \
394 "max-service-time"
395
396struct TaskRef {
397 const Element* t;
398 const Element* p;
399 const Element* g;
400};
401
402/** One task's `A` section, appended to `out`. */
403inline void activity_section(const Element* t, const Element* ta, std::vector<std::string>& out) {
404 check(ta, {}, {"activity", "precedence", "reply-entry"});
405 out.push_back("");
406 out.push_back("A " + show(t, "name"));
407 for (const Element* a : findall(ta, "activity")) {
408 check(a, {LINE_SRVN_ACT_ATTRS, "bound-to-entry"}, {"synch-call", "asynch-call"});
409 const std::string an = name(a);
410 out.push_back(" s " + an + " " + numd(a, "host-demand-mean", "0"));
411 if (get(a, "host-demand-cvsq")) {
412 std::string buf;
413 out.push_back(" c " + an + " " + *num(a, "host-demand-cvsq", buf));
414 }
415 if (upper(get(a, "call-order", "STOCHASTIC")) == "DETERMINISTIC") out.push_back(" f " + an + " 1");
416 static const char* zm[2][2] = {{"Z", "think-time"}, {"M", "max-service-time"}};
417 for (int q = 0; q < 2; ++q) {
418 std::string buf;
419 const std::string* v = num(a, zm[q][1], buf);
420 if (v && *v != "0") out.push_back(" " + std::string(zm[q][0]) + " " + an + " " + *v);
421 }
422 for (const Element* c : kids(a)) {
423 check(c, {"dest", "calls-mean"});
424 const std::string letter = c->name == "synch-call" ? "y" : "z";
425 const std::string dst = name(c, "dest");
426 out.push_back(" " + letter + " " + an + " " + dst + " " + numd(c, "calls-mean", "0"));
427 }
428 }
429 // (activity, [entries]) in first-appearance order
430 std::vector<std::pair<std::string, std::vector<std::string>>> replies;
431 for (const Element* re : findall(ta, "reply-entry")) {
432 check(re, {"name"}, {"reply-activity"});
433 for (const Element* ra : findall(re, "reply-activity")) {
434 check(ra, {"name"});
435 const std::string an = show(ra, "name");
436 std::pair<std::string, std::vector<std::string>>* row = NULL;
437 for (auto& r : replies)
438 if (r.first == an) row = &r;
439 if (!row) {
440 replies.emplace_back(an, std::vector<std::string>());
441 row = &replies.back();
442 }
443 const std::string en = show(re, "name");
444 bool have = false;
445 for (const std::string& x : row->second) have = have || x == en;
446 if (!have) row->second.push_back(en);
447 }
448 }
449 std::map<std::string, std::vector<std::string>> pending(replies.begin(), replies.end());
450 // lqns takes a reply list on the pre side of a connection only, so a terminal activity replies standalone
451 auto ref = [&](const std::string& nm) -> std::string {
452 std::map<std::string, std::vector<std::string>>::iterator it = pending.find(nm);
453 if (it == pending.end()) return nm;
454 const std::string s = nm + "[" + join(it->second, ", ") + "]";
455 pending.erase(it);
456 return s;
457 };
458 const std::string tn = show(t, "name");
459 std::vector<std::string> conns;
460 for (const Element* pr : findall(ta, "precedence")) {
461 const std::vector<const Element*> parts = kids(pr);
462 std::vector<const Element*> pre, post;
463 for (const Element* k : parts) {
464 if (in(k->name, {"pre", "pre-AND", "pre-OR"})) pre.push_back(k);
465 if (starts_with(k->name, "post")) post.push_back(k);
466 }
467 if (pre.size() != 1 || post.size() > 1 || pre.size() + post.size() != parts.size())
468 throw unsup("a <precedence> of task \"" + tn + "\" is not one pre list and at most one post list");
469 const Element* p0 = pre[0];
470 if (p0->name == "pre-AND") check(p0, {"quorum"}, {"activity"});
471 else check(p0, {}, {"activity"});
472 std::vector<std::string> names;
473 for (const Element* x : findall(p0, "activity")) names.push_back(name(x));
474 for (const Element* x : findall(p0, "activity")) check(x, {"name"});
475 if (names.empty() || (p0->name == "pre" && names.size() != 1))
476 throw xerr("a <" + p0->name + "> of task \"" + tn + "\" lists " + std::to_string(names.size()) +
477 " activities");
478 bool anypending = false;
479 for (const std::string& x : names) anypending = anypending || pending.count(x);
480 if (post.empty() && !anypending) continue; // a bare activity says nothing, and lqns refuses it
481 std::vector<std::string> refs;
482 for (const std::string& x : names) refs.push_back(ref(x));
483 std::string s = join(refs, p0->name == "pre-AND" ? " & " : " + ");
484 if (get(p0, "quorum")) {
485 std::string buf;
486 s += " (" + *num(p0, "quorum", buf) + ")";
487 }
488 if (!post.empty()) {
489 const Element* q = post[0];
490 const std::vector<const Element*> items = findall(q, "activity");
491 std::string rhs;
492 if (q->name == "post-OR") {
493 check(q, {}, {"activity"});
494 for (const Element* x : items) check(x, {"name", "prob"});
495 std::vector<std::string> bs;
496 for (const Element* x : items) {
497 const std::string pv = numd(x, "prob", "0");
498 bs.push_back("(" + pv + ") " + name(x));
499 }
500 rhs = join(bs, " + ");
501 } else if (q->name == "post-LOOP") {
502 check(q, {"end"}, {"activity"});
503 if (items.size() != 1)
504 throw unsup("the loop in task \"" + tn + "\" has " + std::to_string(items.size()) +
505 " bodies; SRVN gives a loop one body");
506 check(items[0], {"name", "count"});
507 const std::string cnt = numd(items[0], "count", "1");
508 rhs = cnt + " * " + name(items[0]);
509 if (get(q, "end")) rhs += ", " + name(q, "end");
510 } else if (q->name == "post" || q->name == "post-AND") {
511 check(q, {}, {"activity"});
512 for (const Element* x : items) check(x, {"name"});
513 if (q->name == "post" && items.size() != 1)
514 throw xerr("a <post> of task \"" + tn + "\" lists " + std::to_string(items.size()) +
515 " activities");
516 std::vector<std::string> ns;
517 for (const Element* x : items) ns.push_back(name(x));
518 rhs = join(ns, " & ");
519 } else {
520 throw unsup("<" + q->name + "> in task \"" + tn + "\" has no SRVN spelling");
521 }
522 if (items.empty()) throw xerr("a <" + q->name + "> of task \"" + tn + "\" lists no activities");
523 s += " -> " + rhs;
524 }
525 conns.push_back(s);
526 }
527 for (const auto& r : replies)
528 if (pending.count(r.first)) conns.push_back(ref(r.first));
529 if (!conns.empty()) {
530 out.push_back(" :");
531 for (std::size_t i = 0; i < conns.size(); ++i)
532 out.push_back(" " + conns[i] + (i + 1 < conns.size() ? ";" : ""));
533 }
534 out.push_back("-1");
535}
536
537} // namespace srvn_out
538
539/**
540 * Translate an LQN XML document into SRVN text.
541 *
542 * @param xml_text the LQN XML document
543 * @param name the model name for the header comment; empty means `<lqn-model name>`
544 * (or "model" when the root has none)
545 * @return the SRVN document, newline-terminated
546 * @throws InputError (`LINE:input:Format`) when the XML is malformed
547 * @throws UnsupportedError (`LINE:unsupported:Format`) for content SRVN cannot express
548 */
549inline std::string lqnx2srvn(const std::string& xml_text, const std::string& name = std::string()) {
550 using namespace srvn_out;
551 std::unique_ptr<Element> doc;
552 try {
553 doc = xml::parse(xml_text);
554 } catch (const InputError& e) {
555 throw xerr(std::string("the LQN XML is malformed: ") + e.what());
556 }
557 normalize_tree(doc.get());
558 const Element* root = doc.get();
559 if (root->name != "lqn-model") throw xerr("the root element is <" + root->name + ">, not <lqn-model>");
560 const std::string model_name = name.empty() ? get(root, "name", "model") : name;
561 for (const Element* k : kids(root))
562 if (k->name != "solver-params" && k->name != "processor") throw unsup("<" + k->name + "> has no SRVN spelling");
563 std::vector<std::string> out;
564 out.push_back("# SRVN model " + model_name + ", written by LINE");
565 const Element* sp = find(root, "solver-params");
566 std::string comment;
567 std::vector<std::string> solver = {"1e-05", "50"};
568 if (sp) {
569 check(sp, {"comment", "conv_val", "it_limit", "print_int", "underrelax_coeff"}, {"pragma"});
570 for (const Element* pr : findall(sp, "pragma")) {
571 check(pr, {"param", "value"});
572 out.push_back("#pragma " + get(pr, "param", "") + "=" + get(pr, "value", ""));
573 }
574 comment = get(sp, "comment", "");
575 solver = {numd(sp, "conv_val", "1e-05"), numd(sp, "it_limit", "50")};
576 std::string pb, ub;
577 const std::string* pi = num(sp, "print_int", pb);
578 const std::string* ur = num(sp, "underrelax_coeff", ub);
579 if (pi || ur) solver.push_back(pi ? *pi : std::string("0"));
580 if (ur) solver.push_back(*ur);
581 }
582 if (comment.find('"') != std::string::npos)
583 throw unsup("the solver-params comment contains a double quote, which an SRVN string cannot hold");
584 {
585 std::vector<std::string> words;
586 std::size_t i = 0;
587 while (i < comment.size()) {
588 while (i < comment.size() && srvn_detail::is_space(comment[i])) ++i;
589 std::size_t j = i;
590 while (j < comment.size() && !srvn_detail::is_space(comment[j])) ++j;
591 if (j > i) words.push_back(comment.substr(i, j - i));
592 i = j;
593 }
594 out.push_back("G \"" + join(words, " ") + "\" " + join(solver, " ") + " -1");
595 }
596
597 std::vector<const Element*> procs;
598 std::vector<std::pair<const Element*, const Element*>> groups;
599 std::vector<TaskRef> tasks;
600 for (const Element* p : findall(root, "processor")) {
601 check(p, {"name", "scheduling", "quantum", "multiplicity", "replication", "speed-factor"}, {"task", "group"});
602 procs.push_back(p);
603 for (const Element* k : kids(p)) {
604 if (k->name == "group") {
605 check(k, {"name", "share", "cap"}, {"task"});
606 groups.emplace_back(k, p);
607 for (const Element* t : findall(k, "task")) tasks.push_back(TaskRef{t, p, k});
608 } else {
609 tasks.push_back(TaskRef{k, p, NULL});
610 }
611 }
612 }
613
614 out.push_back("");
615 out.push_back("P " + std::to_string(procs.size()));
616 for (const Element* p : procs) {
617 const std::string sched = lower(get(p, "scheduling", "fcfs"));
618 const char* letter = proc_letter(sched);
619 if (!letter)
620 throw unsup("processor \"" + show(p, "name") + "\" uses scheduling \"" + sched +
621 "\", which SRVN cannot express");
622 std::string s = " p " + srvn_out::name(p) + " " + letter;
623 if (std::string(letter) == "c" || std::string(letter) == "s") {
624 s += " " + numd(p, "quantum", "0");
625 } else if (get(p, "quantum")) {
626 std::string buf;
627 if (std::strtod(num(p, "quantum", buf)->c_str(), NULL) != 0)
628 throw unsup("processor \"" + show(p, "name") + "\" has a quantum, which SRVN gives to \"ps\" and \"cfs\" only");
629 }
630 std::string b1, b2, b3;
631 const std::string* mult = num(p, "multiplicity", b1);
632 if (sched != "inf" && mult && *mult != "1") s += " m " + *mult;
633 const std::string* repl = num(p, "replication", b2);
634 if (repl && *repl != "1") s += " r " + *repl;
635 const std::string* speed = num(p, "speed-factor", b3);
636 if (speed && *speed != "1") s += " R " + *speed;
637 out.push_back(s);
638 }
639 out.push_back("-1");
640
641 if (!groups.empty()) {
642 out.push_back("");
643 out.push_back("U " + std::to_string(groups.size()));
644 for (const auto& gp : groups) {
645 const std::string cap = lower(get(gp.first, "cap", "false"));
646 const std::string gn = srvn_out::name(gp.first);
647 const std::string share = numd(gp.first, "share", "0");
648 out.push_back(" g " + gn + " " + share + (cap == "true" ? " c" : "") + " " + show(gp.second, "name"));
649 }
650 out.push_back("-1");
651 }
652
653 std::vector<std::pair<const Element*, const Element*>> entries;
654 std::vector<std::string> fans;
655 out.push_back("");
656 out.push_back("T " + std::to_string(tasks.size()));
657 for (const TaskRef& tr : tasks) {
658 const Element* t = tr.t;
659 check(t, {"name", "scheduling", "multiplicity", "replication", "think-time", "priority", "queue-length"},
660 {"entry", "task-activities", "fan-in", "fan-out"});
661 const std::string sched = lower(get(t, "scheduling", "fcfs"));
662 const char* letter = task_letter(sched);
663 if (!letter)
664 throw unsup("task \"" + show(t, "name") + "\" uses scheduling \"" + sched + "\", which SRVN cannot express");
665 const std::vector<const Element*> ents = findall(t, "entry");
666 if (ents.empty()) throw xerr("task \"" + show(t, "name") + "\" has no entries");
667 const std::string tn = srvn_out::name(t);
668 std::vector<std::string> ens;
669 for (const Element* e : ents) ens.push_back(srvn_out::name(e));
670 std::string s = " t " + tn + " " + letter + " " + join(ens, " ") + " -1 " + show(tr.p, "name");
671 std::string b1, b2, b3, b4, b5;
672 const std::string* prio = num(t, "priority", b1);
673 if (prio) s += " " + *prio;
674 const std::string* think = num(t, "think-time", b2);
675 if (think && *think != "0") s += " z " + *think;
676 const std::string* mult = num(t, "multiplicity", b3);
677 if (sched != "inf" && mult && *mult != "1") s += " m " + *mult;
678 const std::string* repl = num(t, "replication", b4);
679 if (repl && *repl != "1") s += " r " + *repl;
680 if (tr.g) s += " g " + show(tr.g, "name");
681 const std::string* qlen = num(t, "queue-length", b5);
682 if (qlen) s += " q " + *qlen;
683 out.push_back(s);
684 for (const Element* f : findall(t, "fan-in")) {
685 check(f, {"source", "value"});
686 const std::string src = srvn_out::name(f, "source");
687 fans.push_back(" I " + src + " " + show(t, "name") + " " + numd(f, "value", "1"));
688 }
689 for (const Element* f : findall(t, "fan-out")) {
690 check(f, {"dest", "value"});
691 const std::string dst = srvn_out::name(f, "dest");
692 fans.push_back(" O " + show(t, "name") + " " + dst + " " + numd(f, "value", "1"));
693 }
694 for (const Element* e : ents) entries.emplace_back(e, t);
695 }
696 out.insert(out.end(), fans.begin(), fans.end());
697 out.push_back("-1");
698
699 std::map<std::string, std::string> bound;
700 for (const TaskRef& tr : tasks) {
701 const Element* ta = find(tr.t, "task-activities");
702 if (!ta) continue;
703 for (const Element* a : findall(ta, "activity")) {
704 const std::string* be = get(a, "bound-to-entry");
705 if (be && !bound.count(*be)) bound[*be] = show(a, "name");
706 }
707 }
708 out.push_back("");
709 out.push_back("E " + std::to_string(entries.size()));
710 for (const auto& et : entries) {
711 const Element* e = et.first;
712 check(e, {"name", "type", "open-arrival-rate", "priority", "semaphore", "rwlock"},
713 {"forwarding", "entry-phase-activities"});
714 const std::string en = show(e, "name");
715 std::string b1, b2;
716 const std::string* rate = num(e, "open-arrival-rate", b1);
717 if (rate && *rate != "0") out.push_back(" a " + en + " " + *rate);
718 const std::string* prio = num(e, "priority", b2);
719 if (prio) out.push_back(" p " + en + " " + *prio);
720 if (const std::string* sem = get(e, "semaphore")) {
721 if (!sem_letter(*sem)) throw unsup("entry \"" + en + "\" has semaphore=\"" + *sem + "\"");
722 out.push_back(std::string(" ") + sem_letter(*sem) + " " + en);
723 }
724 if (const std::string* rw = get(e, "rwlock")) {
725 if (!rwlock_letter(*rw)) throw unsup("entry \"" + en + "\" has rwlock=\"" + *rw + "\"");
726 out.push_back(std::string(" ") + rwlock_letter(*rw) + " " + en);
727 }
728 const Element* epa = find(e, "entry-phase-activities");
729 std::map<int, const Element*> acts;
730 if (epa) {
731 check(epa, {}, {"activity"});
732 for (const Element* a : findall(epa, "activity")) {
733 check(a, {LINE_SRVN_ACT_ATTRS}, {"synch-call", "asynch-call"});
734 const int k = static_cast<int>(std::strtod(numd(a, "phase", "1").c_str(), NULL));
735 if (k < 1 || acts.count(k))
736 throw xerr("entry \"" + en + "\" has two activities for phase " + std::to_string(k));
737 acts[k] = a;
738 }
739 }
740 std::map<std::string, std::string>::const_iterator b = bound.find(en);
741 if (b != bound.end()) {
742 if (!acts.empty())
743 throw unsup("entry \"" + en + "\" has both phases and a start activity, which SRVN cannot express");
744 out.push_back(" A " + en + " " + b->second);
745 }
746 if (!acts.empty()) {
747 std::vector<std::pair<std::string, std::vector<std::string>>> lines;
748 std::vector<CallRow> calls;
749 phase_lists(acts, lines, calls);
750 for (const auto& l : lines) out.push_back(" " + l.first + " " + en + " " + join(l.second, " ") + " -1");
751 for (const CallRow& c : calls)
752 out.push_back(" " + c.kind + " " + en + " " + c.dst + " " + join(c.vals, " ") + " -1");
753 }
754 for (const Element* f : findall(e, "forwarding")) {
755 check(f, {"dest", "prob"});
756 const std::string dst = srvn_out::name(f, "dest");
757 out.push_back(" F " + en + " " + dst + " " + numd(f, "prob", "0") + " -1");
758 }
759 }
760 out.push_back("-1");
761
762 for (const TaskRef& tr : tasks) {
763 const Element* ta = find(tr.t, "task-activities");
764 if (ta) activity_section(tr.t, ta, out);
765 }
766 std::string text;
767 for (const std::string& l : out) {
768 text += l;
769 text += '\n';
770 }
771 return text;
772}
773
774#undef LINE_SRVN_ACT_ATTRS
775
776/**
777 * Write a layered model as SRVN text: `write_lqnx`, then `lqnx2srvn`, with LF line endings.
778 *
779 * @param m the intermediate model
780 * @param path file to create
781 * @param use_abstract_names rename elements P1/T1/E1/A1 first, which also makes every name an LQNS identifier
782 * @throws UnsupportedError (`LINE:unsupported:Format`) for content SRVN cannot express
783 */
784template <class T>
785void write_srvn(const LqnModel<T>& m, const std::string& path, bool use_abstract_names = false) {
786 const std::string xml_text = lqnx_to_string(m, m.name.empty() ? std::string("LQN") : m.name, use_abstract_names);
787 const std::string text = lqnx2srvn(xml_text, m.name);
788 std::ofstream f(path.c_str(), std::ios::binary);
789 if (!f) throw InputError(ErrorKind{"File"}, "cannot write " + path);
790 f << text;
791}
792
793} // namespace lqn
794} // namespace line
795
796#endif // LINE_LANG_LQN_SRVN_WRITER_H
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
A refusal: the feature, method or analysis is not covered: LINE:unsupported:<kind>.
Definition error.h:117
UnsupportedError(const std::string &what)
Definition error.h:119
The exception types the port throws.
LqnModel -> .lqnx, a port of matlab/src/lang/layered/@LayeredNetwork/writeXML.m.
bool is_digit(char c)
Definition srvn_reader.h:61
bool is_space(char c)
Python's str.isspace over ASCII, which also counts the four separators 0x1c-0x1f.
Definition srvn_reader.h:57
bool in(const std::string &s, std::initializer_list< const char * > set)
std::vector< const Element * > findall(const Element *el, const std::string &tag)
void normalize_tree(Element *el)
bool srvn_name_ok(const std::string &nm)
[A-Za-z_][A-Za-z0-9_]+$ under re.match, where $ also matches before a final newline.
const char * task_letter(const std::string &s)
const char * proc_letter(const std::string &s)
const char * rwlock_letter(const std::string &s)
std::string normalize_attr(const std::string &v)
An attribute value as expat delivers it: literal tab, CR and LF become spaces (a CR LF pair one space...
bool is_output_tag(const std::string &tag)
Output requests: read and dropped, as the reader drops H, R and observations.
std::string numd(const Element *el, const std::string &key, const char *dflt)
_num with a default, which never returns None.
void check(const Element *el, std::initializer_list< const char * > allowed, std::initializer_list< const char * > kidtags={})
void phase_lists(const std::map< int, const Element * > &acts, std::vector< std::pair< std::string, std::vector< std::string > > > &lines, std::vector< CallRow > &calls)
acts maps phase -> <activity>; the E-section lines of one entry's phases.
std::string join(const std::vector< std::string > &v, const std::string &sep)
void activity_section(const Element *t, const Element *ta, std::vector< std::string > &out)
One task's A section, appended to out.
InputError xerr(const std::string &msg)
Definition srvn_writer.h:59
bool py_float(const std::string &text, double &x)
Python's float(text): surrounding whitespace, underscores between digits, inf/nan.
std::string show(const Element *el, const std::string &key)
's' % el.get(key): None prints as "None".
Definition srvn_writer.h:75
std::string lower(std::string s)
Definition srvn_writer.h:80
std::vector< const Element * > kids(const Element *el)
Model children of el: results and output requests are skipped.
const std::string * num(const Element *el, const std::string &key, std::string &buf, const char *dflt=NULL)
_num: the attribute as format_number(float(text)), or dflt (null = None) when absent.
const std::string * get(const Element *el, const std::string &key)
el.get(key), with Python's None as a null pointer.
Definition srvn_writer.h:62
std::string upper(std::string s)
Definition srvn_writer.h:86
bool starts_with(const std::string &s, const char *p)
Definition srvn_writer.h:92
UnsupportedError unsup(const std::string &msg)
Definition srvn_writer.h:55
const Element * find(const Element *el, const std::string &tag)
el.find(tag): the first direct child with that tag, or null.
const char * sem_letter(const std::string &s)
std::string name(const Element *el, const std::string &key="name")
void utf8(std::string &out, unsigned long cp)
Definition srvn_writer.h:94
std::string lqnx2srvn(const std::string &xml_text, const std::string &name=std::string())
Translate an LQN XML document into SRVN text.
std::string format_number(double x)
Shortest C g spelling that round-trips x; the four codebases share this rule.
std::string lqnx_to_string(const LqnModel< T > &m, const std::string &model_name=std::string("LQN"), bool use_abstract_names=false)
The .lqnx document as a STRING, for a caller with no file to write to – the CLI's model-generation mo...
Definition lqn_writer.h:666
void write_srvn(const LqnModel< T > &m, const std::string &path, bool use_abstract_names=false)
Write a layered model as SRVN text: write_lqnx, then lqnx2srvn, with LF line endings.
std::unique_ptr< Element > parse(const std::string &text)
Parse a whole XML document and return its root element.
Definition xml.h:184
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
SRVN input format (the LQNS .in / .lqn text grammar) for layered models, the C++ twin of python/line_...
#define LINE_SRVN_ACT_ATTRS
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:411
std::string name
LayeredNetwork.getName(); empty when unnamed.
Definition lqn_reader.h:413
std::vector< std::string > vals
std::vector< std::pair< std::string, std::string > > attrs
Definition xml.h:56
std::string name
Definition xml.h:55
std::vector< std::unique_ptr< Element > > children
Definition xml.h:57
std::vector< const Element * > child_tags(const std::string &tag) const
Direct children with the given tag, in document order.
Definition xml.h:83
A minimal XML DOM: read for the .lqnx interchange format, write for the JMT .jsimg and ....