LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
lqn_json_reader.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_IO_LQN_JSON_READER_H
6#define LINE_IO_LQN_JSON_READER_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * `model.json` with `type: "LayeredNetwork"` -> `LqnStruct`, via `LqnBuilder`.
12 *
13 * WHY THIS EXISTS BESIDE `lqn_reader.h`. The .lqnx reader covers the LQNS file
14 * format, which is what an external tool writes; this covers the LINE
15 * interchange format, which is what `linemodel_save.m`, `save_model` and the
16 * JAR `LineModelIO` write for a `LayeredNetwork`. The two are not
17 * interchangeable in either direction: .lqnx cannot carry a non-reference
18 * task's think time, a CacheTask, an ItemEntry or a SetupTask, and model.json
19 * carries all four. Without this reader a layered model could reach the C++
20 * port only by being re-exported through XML, losing exactly those constructs.
21 *
22 * ORDERING IS PART OF THE FORMAT. `LqnBuilder` assigns indices in call order,
23 * and every later array -- the layer decomposition, the visit matrix, the
24 * result tables -- is keyed on them. The document's own order is used
25 * throughout (hosts, then tasks, then entries, then activities), which is the
26 * order the writers emit and therefore the order MATLAB's own `getStruct`
27 * produced before serialization.
28 *
29 * WHAT IS READ, following `linemodel_load.m::json2layered`: fan-in and fan-out
30 * (`fanIn`/`fanOut`, a map from the peer task to the count) and the named
31 * admission constraints of hosts and tasks, which `lqn_finalize` resolves as it
32 * does for the .lqnx reader; a host's `quantum` (0.001 when absent, MATLAB's
33 * default) and `speedFactor`; the activity `thinkTime` the JAR writes. An
34 * explicit-form precedence may spell its types as the JAR and the Workflow wire
35 * do (`pre`, `post-OR`), as Python's aliases (`and-fork`, `or-join`, `loop`) or
36 * as the enum names (`PRE_SEQ`); an unknown spelling is refused by name, where
37 * MATLAB would read it as `pre`.
38 */
39
40#include <map>
41#include <string>
42#include <vector>
43
45#include <cctype>
46#include <fstream>
47
51#include "line/util/error.h"
52
53namespace line {
54namespace io {
55
56namespace detail {
57
58/** The `scheduling` name of a host or task, upper case as the writers emit it. */
59inline lang::SchedStrategy lqn_sched_from_json(const std::string& s) {
60 typedef lang::SchedStrategy S;
61 if (s == "INF" || s == "inf") return S::INF;
62 if (s == "FCFS" || s == "fcfs") return S::FCFS;
63 if (s == "PS" || s == "ps") return S::PS;
64 if (s == "HOL" || s == "hol") return S::HOL;
65 if (s == "REF" || s == "ref") return S::REF;
66 if (s == "SIRO" || s == "siro") return S::SIRO;
67 if (s == "LCFS" || s == "lcfs") return S::LCFS;
68 if (s == "LCFSPR" || s == "lcfspr") return S::LCFSPR;
69 throw UnsupportedError("lqn_json_reader: unsupported scheduling discipline '" + s + "'");
70}
71
72/**
73 * The wire's infinite multiplicity.
74 *
75 * The writers emit `Integer.MAX_VALUE` as a literal rather than a float
76 * infinity, because an infinity would not survive an integer-valued export;
77 * both spellings are accepted here and both mean the same thing.
78 */
79inline double lqn_mult_from_json(const json& v) {
80 if (v.is_string()) {
81 const std::string s = v.get<std::string>();
82 if (s == "Infinity" || s == "inf") return std::numeric_limits<double>::infinity();
83 return std::atof(s.c_str());
84 }
85 const double m = v.get<double>();
86 return m >= 2147483647.0 ? std::numeric_limits<double>::infinity() : m;
87}
88
89/**
90 * Explicit-form precedence type -> `PrecedenceType`, over the JAR spelling
91 * (`ActivityPrecedenceType.java`, also the Workflow wire), Python's aliases and the enum names.
92 */
93inline lang::PrecedenceType lqn_prectype_from_json(const std::string& s, bool pre) {
94 typedef lang::PrecedenceType P;
95 if (pre) {
96 if (s == "pre" || s == "PRE_SEQ") return P::PRE_SEQ;
97 if (s == "pre-AND" || s == "and-join" || s == "PRE_AND") return P::PRE_AND;
98 if (s == "pre-OR" || s == "or-join" || s == "PRE_OR") return P::PRE_OR;
99 } else {
100 if (s == "post" || s == "POST_SEQ") return P::POST_SEQ;
101 if (s == "post-AND" || s == "and-fork" || s == "POST_AND") return P::POST_AND;
102 if (s == "post-OR" || s == "or-fork" || s == "POST_OR") return P::POST_OR;
103 if (s == "post-LOOP" || s == "loop" || s == "POST_LOOP") return P::POST_LOOP;
104 if (s == "post-CACHE" || s == "POST_CACHE") return P::POST_CACHE;
105 }
106 throw UnsupportedError(std::string("lqn_json_reader: unknown ") + (pre ? "preType" : "postType") +
107 " '" + s + "'; the wire spells pre, pre-AND, pre-OR, post, post-AND, "
108 "post-OR, post-LOOP or post-CACHE");
109}
110
111/** `apply_lincon`: replay named admission-constraint rows onto a host or task. */
112template <class T>
113void lqn_apply_lincon(lqn::LqnBuilder<T>& b, const std::string& elem, const json& rows) {
114 for (const json& row : rows) {
115 std::vector<std::string> ops;
116 if (row.at("operands").is_string()) ops.push_back(row.at("operands").get<std::string>());
117 else
118 for (const json& o : row.at("operands")) ops.push_back(o.get<std::string>());
119 // A row with no coefficients weighs every operand by one, as the JAR's addConstraintByName does.
120 std::vector<T> coeffs = row.contains("coeffs") ? num_vec_from_json<T>(row.at("coeffs"))
121 : std::vector<T>(ops.size(), num_traits<T>::from_int(1));
122 b.add_constraint(elem, ops, coeffs, num_traits<T>::from_double(row.at("cap").get<double>()));
123 }
124}
125
126/** A `fanIn`/`fanOut` map {peer task: count} as the declared (name, value) pairs. */
127inline std::vector<std::pair<std::string, double>> lqn_fan_from_json(const json& obj) {
128 std::vector<std::pair<std::string, double>> out;
129 for (json::const_iterator it = obj.begin(); it != obj.end(); ++it)
130 out.push_back(std::make_pair(it.key(), it.value().get<double>()));
131 return out;
132}
133
134/**
135 * The AND-join quorum of a precedence object, 0 for none.
136 *
137 * `preParams: [q]` is the wire key in both the explicit and the typed form. Only
138 * a ONE-element list is a quorum: a list with one entry per branch is not. A
139 * quorum of every branch is the plain join.
140 */
141template <class T>
142std::size_t lqn_quorum_from_json(const json& p, std::size_t nbranches) {
143 if (!p.contains("preParams")) return 0;
144 const json& v = p.at("preParams");
145 double qd = 0.0;
146 if (v.is_number()) qd = v.get<double>();
147 else if (v.is_array() && v.size() == 1 && v[0].is_number()) qd = v[0].get<double>();
148 else return 0;
149 return (qd > 0 && qd < double(nbranches)) ? std::size_t(qd + 0.5) : 0;
150}
151
152} // namespace detail
153
154/**
155 * Build the intermediate `LqnModel<T>` from a parsed model.json envelope carrying
156 * a LayeredNetwork. The struct loses the declared reply activities, which the
157 * source-code generators in code_gen.h need, so the model is exposed as well.
158 */
159template <class T>
161 using detail::json;
162 const json& model = root.contains("model") ? root.at("model") : root;
163 const std::string mtype = model.value("type", std::string("LayeredNetwork"));
164 if (mtype != "LayeredNetwork")
165 throw UnsupportedError("lqn_json_reader: model type '" + mtype +
166 "' is not a LayeredNetwork; a Network model is read by "
167 "network_reader.h");
168
170
171 // -- hosts (processors) --------------------------------------------------
172 // `hosts` is the LINE spelling and `processors` the LQNS one; the schema
173 // declares both and a writer may use either.
174 const json empty = json::array();
175 const json& hosts = model.contains("hosts")
176 ? model.at("hosts")
177 : (model.contains("processors") ? model.at("processors") : empty);
178 std::vector<std::pair<double, double>> quantum_speed; // per host, patched onto the model below
179 std::vector<std::pair<std::string, const json*>> host_rows;
180 for (const json& h : hosts) {
181 const std::string name = h.at("name").get<std::string>();
182 const lang::SchedStrategy sched =
183 detail::lqn_sched_from_json(h.value("scheduling", std::string("PS")));
184 const double mult = h.contains("multiplicity")
185 ? detail::lqn_mult_from_json(h.at("multiplicity"))
186 : 1.0;
187 b.processor(name, mult, sched, h.value("replication", 1.0));
188 quantum_speed.push_back(
189 std::make_pair(h.value("quantum", 0.001), h.value("speedFactor", 1.0)));
190 if (h.contains("admissionConstraints"))
191 host_rows.push_back(std::make_pair(name, &h.at("admissionConstraints")));
192 }
193
194 // -- tasks ---------------------------------------------------------------
195 std::vector<std::vector<std::pair<std::string, double>>> fan_in, fan_out;
196 std::vector<std::pair<std::string, const json*>> task_rows; // applied once every entry exists
197 for (const json& t : model.at("tasks")) {
198 const std::string name = t.at("name").get<std::string>();
199 fan_in.push_back(t.contains("fanIn") ? detail::lqn_fan_from_json(t.at("fanIn"))
200 : std::vector<std::pair<std::string, double>>());
201 fan_out.push_back(t.contains("fanOut") ? detail::lqn_fan_from_json(t.at("fanOut"))
202 : std::vector<std::pair<std::string, double>>());
203 if (t.contains("admissionConstraints"))
204 task_rows.push_back(std::make_pair(name, &t.at("admissionConstraints")));
205 const std::string on = t.at("host").get<std::string>();
206 const lang::SchedStrategy sched =
207 detail::lqn_sched_from_json(t.value("scheduling", std::string("FCFS")));
208 const double mult = t.contains("multiplicity")
209 ? detail::lqn_mult_from_json(t.at("multiplicity"))
210 : 1.0;
211 const double repl = t.value("replication", 1.0);
212 const std::string kind = t.value("taskType", std::string());
213 if (kind == "CacheTask") {
214 std::vector<int> cap;
215 if (t.at("cacheCapacity").is_array())
216 cap = t.at("cacheCapacity").get<std::vector<int> >();
217 else
218 cap.push_back(t.at("cacheCapacity").get<int>());
219 b.cache_task(name, mult, sched, on, t.at("totalItems").get<std::size_t>(), cap,
220 detail::replacement_from_json(
221 t.value("replacementStrategy", std::string("FIFO"))),
222 repl);
223 } else {
224 b.task(name, mult, sched, on, repl);
225 }
226 // `priority` is Task.setPriority; the JAR writes it only when nonzero (LineModelIO)
227 if (t.contains("priority")) b.task_priority(name, static_cast<int>(t.at("priority").get<double>()));
228 // A think time is accepted on ANY task here, as the reference API
229 // allows and as .lqnx cannot express.
230 if (t.contains("thinkTime"))
231 b.think_time(name, detail::dist_from_json<T>(t.at("thinkTime")));
232 else if (t.contains("thinkTimeMean") && t.at("thinkTimeMean").get<double>() > 0)
234 t.at("thinkTimeMean").get<double>())));
235 // BOTH SPELLINGS ARE ACCEPTED, the object form PREFERRED. MATLAB,
236 // Python and this port all write `setupTime`/`delayOffTime` as full
237 // distributions, so that is the canonical wire form; the JAR writes
238 // `setupTimeMean`+`setupTimeSCV` instead. Documents already written in
239 // the field carry only the scalars, and without this fallback both
240 // fields vanish on read and a SetupTask arrives with no setup time at
241 // all -- silently, which is the failure mode `.lqnx` already has. An
242 // SCV cannot be recovered from a mean, so the scalar path reconstructs
243 // an exponential and claims no more, exactly as `thinkTimeMean` does.
244 const bool has_setup_obj = t.contains("setupTime");
245 const bool has_setup_mean =
246 t.contains("setupTimeMean") && t.at("setupTimeMean").get<double>() > 0;
247 if (has_setup_obj || has_setup_mean) {
248 const bool has_off_obj = t.contains("delayOffTime");
249 const bool has_off_mean =
250 t.contains("delayOffTimeMean") && t.at("delayOffTimeMean").get<double>() > 0;
251 if (!has_off_obj && !has_off_mean)
252 throw InputError("lqn_json_reader: task '" + name +
253 "' declares a setup time with no delay-off time; a server that "
254 "never shuts down pays the setup at most once");
255 const lang::Distrib<T> su =
256 has_setup_obj ? detail::dist_from_json<T>(t.at("setupTime"))
258 t.at("setupTimeMean").get<double>()));
259 const lang::Distrib<T> doff =
260 has_off_obj ? detail::dist_from_json<T>(t.at("delayOffTime"))
262 t.at("delayOffTimeMean").get<double>()));
263 b.setup_time(name, su, doff);
264 }
265 }
266
267 // -- entries -------------------------------------------------------------
268 for (const json& e : model.at("entries")) {
269 const std::string name = e.at("name").get<std::string>();
270 const std::string on = e.at("task").get<std::string>();
271 if (e.value("entryType", std::string()) == "ItemEntry") {
272 const std::size_t card = e.at("totalItems").get<std::size_t>();
273 // The popularity crosses as a DISTRIBUTION (a Zipf, typically) and
274 // the builder wants the pmf over the items, which is the same
275 // object the cache node's `pread` is.
276 std::vector<T> pop;
277 if (e.contains("accessProb"))
278 pop = detail::pmf_from_json<T>(e.at("accessProb"), card);
279 if (pop.empty())
280 pop.assign(card, num_traits<T>::from_double(1.0 / double(card)));
281 b.item_entry(name, on, card, pop);
282 } else {
283 b.entry(name, on);
284 }
285 if (e.contains("arrival"))
286 b.open_arrival(name, detail::dist_from_json<T>(e.at("arrival")));
287 }
288
289 // -- activities ----------------------------------------------------------
290 for (const json& a : model.at("activities")) {
291 const std::string name = a.at("name").get<std::string>();
292 const std::string on = a.at("task").get<std::string>();
293 // An activity with no host demand is an IMMEDIATE one; the writers omit
294 // the key in exactly that case, so the absence is the value.
295 const lang::Distrib<T> dem = a.contains("hostDemand")
296 ? detail::dist_from_json<T>(a.at("hostDemand"))
298 b.activity(name, dem, on);
299 if (a.contains("thinkTime")) b.act_think_time(name, detail::dist_from_json<T>(a.at("thinkTime")));
300 if (a.contains("callOrder")) b.call_order(name, a.at("callOrder").get<std::string>());
301 if (a.contains("boundToEntry"))
302 b.bound_to(name, a.at("boundToEntry").get<std::string>());
303 else if (a.contains("boundTo"))
304 b.bound_to(name, a.at("boundTo").get<std::string>());
305 if (a.contains("repliesTo")) b.replies_to(name, a.at("repliesTo").get<std::string>());
306 const char* kCallKey[2] = {"synchCalls", "asynchCalls"};
307 for (int which = 0; which < 2; ++which) {
308 if (!a.contains(kCallKey[which])) continue;
309 for (const json& c : a.at(kCallKey[which])) {
310 // `dest` is the LINE spelling and `entry` the schema's.
311 const std::string dest = c.contains("dest")
312 ? c.at("dest").get<std::string>()
313 : c.at("entry").get<std::string>();
314 const T mean = num_traits<T>::from_double(c.value("mean", 1.0));
315 if (which == 0) b.sync_call(name, dest, mean);
316 else b.async_call(name, dest, mean);
317 }
318 }
319 }
320
321 // Entry forwarding is declared on the ENTRY and resolved after every entry
322 // exists, since it names one.
323 for (const json& e : model.at("entries")) {
324 if (!e.contains("forwarding")) continue;
325 const std::string name = e.at("name").get<std::string>();
326 for (const json& f : e.at("forwarding"))
327 b.forward(name, f.at("dest").get<std::string>(),
328 num_traits<T>::from_double(f.value("prob", 1.0)));
329 }
330
331 // -- precedences ---------------------------------------------------------
332 //
333 // The TYPED form the writers emit: one `type` naming the pre/post pair,
334 // plus the activity list with the pre-activities first. The EXPLICIT form
335 // (`preType`/`postType` with separate lists) is the schema's other spelling
336 // and is mapped onto the same builder calls.
337 if (model.contains("precedences")) {
338 for (const json& p : model.at("precedences")) {
339 const std::string kind = p.value("type", std::string());
340 if (kind.empty()) {
341 // Explicit form, in any of the three spellings lqn_prectype_from_json accepts.
342 typedef lang::PrecedenceType P;
343 const P pre = detail::lqn_prectype_from_json(p.value("preType", std::string("pre")), true);
344 const P post =
345 detail::lqn_prectype_from_json(p.value("postType", std::string("post")), false);
346 std::vector<std::string> pres, posts;
347 if (p.contains("preActs"))
348 for (const json& x : p.at("preActs")) pres.push_back(x.get<std::string>());
349 if (p.contains("postActs"))
350 for (const json& x : p.at("postActs")) posts.push_back(x.get<std::string>());
351 const std::vector<T> post_params = p.contains("postParams")
352 ? detail::num_vec_from_json<T>(p.at("postParams"))
353 : std::vector<T>();
354 if (pre == P::PRE_SEQ && post == P::POST_SEQ) {
355 b.serial(pres.at(0), posts.at(0));
356 } else if (pre == P::PRE_SEQ && post == P::POST_AND) {
357 b.and_fork(pres.at(0), posts);
358 } else if (pre == P::PRE_AND && post == P::POST_SEQ) {
359 b.and_join(pres, posts.at(0), detail::lqn_quorum_from_json<T>(p, pres.size()));
360 } else if (pre == P::PRE_OR && post == P::POST_SEQ) {
361 b.or_join(pres, posts.at(0));
362 } else if (pre == P::PRE_SEQ && post == P::POST_OR) {
363 // probabilities, else postParams, else uniform: linemodel_load.m's order
364 std::vector<T> probs;
365 if (p.contains("probabilities"))
366 probs = detail::num_vec_from_json<T>(p.at("probabilities"));
367 if (probs.empty()) probs = post_params;
368 if (probs.empty())
369 probs.assign(posts.size(), num_traits<T>::from_double(1.0 / double(posts.size())));
370 b.or_fork(pres.at(0), posts, probs);
371 } else if (pre == P::PRE_SEQ && post == P::POST_LOOP) {
372 // `postActs` is the loop BODY followed by the activity the
373 // loop exits to, which is how `ActivityPrecedence.Loop`
374 // stores it and how the builder takes it back apart.
375 if (posts.size() < 2)
376 throw InputError(
377 "lqn_json_reader: a loop's postActs is the body followed by the "
378 "activity it exits to, so it holds at least two entries");
379 T count = num_traits<T>::from_double(p.value("loopCount", 1.0));
380 if (!p.contains("loopCount") && !post_params.empty()) count = post_params[0];
381 b.loop(pres.at(0), std::vector<std::string>(posts.begin(), posts.end() - 1),
382 posts.back(), count);
383 } else if (pre == P::PRE_SEQ && post == P::POST_CACHE) {
384 if (posts.size() < 2)
385 throw InputError("lqn_json_reader: a post-CACHE precedence lists the hit and miss branches");
386 b.cache_access(pres.at(0), posts.at(0), posts.at(1));
387 } else {
388 throw UnsupportedError("lqn_json_reader: precedence " + p.value("preType", std::string("pre")) +
389 " -> " + p.value("postType", std::string("post")) +
390 " has no builder counterpart");
391 }
392 continue;
393 }
394 std::vector<std::string> acts;
395 for (const json& x : p.at("activities")) acts.push_back(x.get<std::string>());
396 if (kind == "Serial") {
397 // A serial chain is written as one list, and the builder takes
398 // it a pair at a time.
399 for (std::size_t i = 0; i + 1 < acts.size(); ++i) b.serial(acts[i], acts[i + 1]);
400 } else if (kind == "AndFork") {
401 b.and_fork(acts.at(0), std::vector<std::string>(acts.begin() + 1, acts.end()));
402 } else if (kind == "AndJoin") {
403 b.and_join(std::vector<std::string>(acts.begin(), acts.end() - 1), acts.back(),
404 detail::lqn_quorum_from_json<T>(p, acts.size() - 1));
405 } else if (kind == "OrFork") {
406 std::vector<T> probs;
407 if (p.contains("probabilities"))
408 probs = detail::num_vec_from_json<T>(p.at("probabilities"));
409 b.or_fork(acts.at(0), std::vector<std::string>(acts.begin() + 1, acts.end()),
410 probs);
411 } else if (kind == "OrJoin") {
412 b.or_join(std::vector<std::string>(acts.begin(), acts.end() - 1), acts.back());
413 } else if (kind == "Loop") {
414 // The trigger is written separately from the body, so `acts` is
415 // the body alone.
416 if (!p.contains("preActivity"))
417 throw InputError(
418 "lqn_json_reader: a Loop precedence carries its trigger as 'preActivity'");
419 if (acts.size() < 2)
420 throw InputError(
421 "lqn_json_reader: a Loop precedence lists the body followed by the "
422 "activity it exits to, so it holds at least two entries");
423 b.loop(p.at("preActivity").get<std::string>(),
424 std::vector<std::string>(acts.begin(), acts.end() - 1), acts.back(),
425 num_traits<T>::from_double(p.value("loopCount", 1.0)));
426 } else if (kind == "CacheAccess") {
427 if (acts.size() < 3)
428 throw InputError(
429 "lqn_json_reader: a CacheAccess precedence names the read activity plus "
430 "its hit and miss branches");
431 b.cache_access(acts.at(0), acts.at(1), acts.at(2));
432 } else {
433 throw UnsupportedError("lqn_json_reader: precedence type '" + kind +
434 "' has no builder counterpart");
435 }
436 }
437 }
438 for (const auto& r : host_rows) detail::lqn_apply_lincon(b, r.first, *r.second);
439 for (const auto& r : task_rows) detail::lqn_apply_lincon(b, r.first, *r.second);
440 lqn::LqnModel<T> m = b.model();
441 for (std::size_t i = 0; i < quantum_speed.size() && i < m.procs.size(); ++i) {
442 m.procs[i].quantum = quantum_speed[i].first;
443 m.procs[i].speed_factor = quantum_speed[i].second;
444 }
445 for (std::size_t i = 0; i < m.tasks.size() && i < fan_in.size(); ++i) {
446 m.tasks[i].fanin = fan_in[i];
447 m.tasks[i].fanout = fan_out[i];
448 }
449 if (model.contains("name") && model.at("name").is_string()) m.name = model.at("name").get<std::string>();
450 return m;
451}
452
453/**
454 * Build an `LqnStruct<T>` from a parsed model.json envelope carrying a
455 * LayeredNetwork.
456 */
457template <class T>
461
462/**
463 * True when a DOCUMENT ALREADY IN MEMORY is a LayeredNetwork model.json.
464 *
465 * The path form below reads the file twice, which is right for a file and
466 * impossible for a stream: standard input can be drained only once, so a caller
467 * holding piped text has nothing to hand the path form. Without this the CLI
468 * could not tell a layered model from a flat one on stdin, and refused every
469 * piped LayeredNetwork with a message about the SOLVER rather than the reader.
470 */
471inline bool is_layered_json_text(const std::string& text) {
472 std::size_t i = 0;
473 while (i < text.size() && std::isspace(static_cast<unsigned char>(text[i]))) ++i;
474 if (i >= text.size() || text[i] != '{') return false;
475 detail::json root;
476 try {
477 root = detail::json::parse(text);
478 } catch (const detail::json::parse_error&) {
479 return false;
480 }
481 const detail::json& model = root.contains("model") ? root.at("model") : root;
482 return model.value("type", std::string()) == "LayeredNetwork";
483}
484
485/**
486 * True when a file is a LayeredNetwork model.json rather than an .lqnx.
487 *
488 * Decided on the CONTENT, not the extension: `linemodel_save` writes `.json`
489 * for both model kinds and the two readers cannot be told apart by name. The
490 * first non-space character settles it -- `{` is JSON, `<` is XML -- and the
491 * `type` field settles which JSON.
492 */
493inline bool is_layered_json(const std::string& path) {
494 std::ifstream in(path.c_str());
495 if (!in) return false;
496 char c = 0;
497 while (in.get(c) && std::isspace(static_cast<unsigned char>(c))) {}
498 if (c != '{') return false;
499 in.seekg(0);
500 detail::json root;
501 try {
502 in >> root;
503 } catch (const detail::json::parse_error&) {
504 return false;
505 }
506 const detail::json& model = root.contains("model") ? root.at("model") : root;
507 return model.value("type", std::string()) == "LayeredNetwork";
508}
509
510/** Parse a LayeredNetwork model.json file into its intermediate `LqnModel<T>`. */
511template <class T>
512lqn::LqnModel<T> read_lqn_json_model(const std::string& path) {
513 std::ifstream in(path.c_str());
514 if (!in) throw InputError("lqn_json_reader: cannot open " + path);
515 detail::json root;
516 try {
517 in >> root;
518 } catch (const detail::json::parse_error& e) {
519 throw InputError("lqn_json_reader: malformed JSON in " + path + ": " + e.what());
520 }
521 return build_lqn_model_from_json<T>(root);
522}
523
524/** Parse a LayeredNetwork model.json file into an `LqnStruct<T>`. */
525template <class T>
526lqn::LqnStruct<T> read_lqn_json(const std::string& path) {
527 std::ifstream in(path.c_str());
528 if (!in) throw InputError("lqn_json_reader: cannot open " + path);
529 detail::json root;
530 try {
531 in >> root;
532 } catch (const detail::json::parse_error& e) {
533 throw InputError("lqn_json_reader: malformed JSON in " + path + ": " + e.what());
534 }
535 return build_lqn_from_json<T>(root);
536}
537
538/**
539 * Read a layered model from either interchange: the LINE `model.json` or the
540 * LQNS `.lqnx`.
541 *
542 * Every layered entry point goes through here, so a caller never has to know
543 * which of the two it was handed -- and, more to the point, a `model.json`
544 * written by `linemodel_save` for a LayeredNetwork stops being unreadable
545 * merely because the XML reader was the only one wired up.
546 */
547template <class T>
548lqn::LqnStruct<T> read_layered_model(const std::string& path) {
549 return is_layered_json(path) ? read_lqn_json<T>(path) : lqn::read_lqnx<T>(path);
550}
551
552} // namespace io
553} // namespace line
554
555#endif // LINE_IO_LQN_JSON_READER_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
void bound_to(const std::string &act, const std::string &entry_name)
Bind an activity to an entry: it is the entry's first activity.
std::size_t activity(const std::string &name, const Distrib< T > &hostdem, const std::string &on_task)
Add an activity on a task, with its host demand.
void act_think_time(const std::string &act, const Distrib< T > &d)
An activity think time, in series with the host demand.
void loop(const std::string &pre, const std::vector< std::string > &body, const std::string &end, const T &count)
pre -> body, repeated count times in expectation, then -> end.
void open_arrival(const std::string &entry_name, const Distrib< T > &d)
An open arrival stream at an entry.
void setup_time(const std::string &task_name, const Distrib< T > &setup, const Distrib< T > &delayoff)
A SetupTask: a server that powers down when idle and pays to restart.
std::size_t processor(const std::string &name, double mult, SchedStrategy sched, double repl=1.0)
Add a processor.
Definition lqn_builder.h:49
void task_priority(const std::string &task_name, int priority)
Task.setPriority, on the lqns scale the .lqnx priority attribute carries: a LARGER value is served fi...
void replies_to(const std::string &act, const std::string &entry_name)
Mark an activity as the one that replies to an entry.
void forward(const std::string &src_entry, const std::string &dest_entry, const T &prob)
Forwarding: whenever src_entry is invoked, with probability prob the request is handed onward to dest...
void and_fork(const std::string &pre, const std::vector< std::string > &posts)
pre -> every post, concurrently.
void cache_access(const std::string &pre, const std::string &hit, const std::string &miss)
ActivityPrecedence.CacheAccess(pre, {hit, miss}).
std::size_t cache_task(const std::string &name, double mult, SchedStrategy sched, const std::string &on_processor, std::size_t nitems, const std::vector< int > &itemcap, ReplacementStrategy replacestrat, double repl=1.0)
A CacheTask: a task whose entries are looked up in a cache of nitems.
Definition lqn_builder.h:94
void async_call(const std::string &act, const std::string &dest_entry, const T &mean)
An asynchronous call from an activity to an entry of another task.
void and_join(const std::vector< std::string > &pres, const std::string &post, std::size_t quorum=0)
all pres (or quorum of them) -> post.
void serial(const std::string &pre, const std::string &post)
pre -> post, a plain sequence.
void sync_call(const std::string &act, const std::string &dest_entry, const T &mean)
A synchronous call from an activity to an entry of another task.
void call_order(const std::string &act, const std::string &order)
Activity.setCallOrder: STOCHASTIC or DETERMINISTIC, any other value reading as STOCHASTIC.
void think_time(const std::string &task_name, const Distrib< T > &d)
Set a task's think time.
Definition lqn_builder.h:82
const LqnModel< T > & model() const
void or_fork(const std::string &pre, const std::vector< std::string > &posts, const std::vector< T > &probs)
pre -> one of the posts, with the given branch probabilities.
void or_join(const std::vector< std::string > &pres, const std::string &post)
any of the pres -> post.
std::size_t item_entry(const std::string &name, const std::string &on_task, std::size_t cardinality, const std::vector< T > &popularity)
An ItemEntry: the entry a cache read enters, over cardinality items.
std::size_t entry(const std::string &name, const std::string &on_task)
Add an entry on a task.
std::size_t task(const std::string &name, double mult, SchedStrategy sched, const std::string &on_processor, double repl=1.0)
Add a task on a processor.
Definition lqn_builder.h:61
The exception types the port throws.
Build a layered queueing network in code, as the MATLAB constructors do.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
LayeredNetworkStruct, the flattened description of a layered queueing network.
bool is_layered_json(const std::string &path)
True when a file is a LayeredNetwork model.json rather than an .lqnx.
lqn::LqnModel< T > read_lqn_json_model(const std::string &path)
Parse a LayeredNetwork model.json file into its intermediate LqnModel<T>.
lqn::LqnStruct< T > read_lqn_json(const std::string &path)
Parse a LayeredNetwork model.json file into an LqnStruct<T>.
lqn::LqnStruct< T > read_layered_model(const std::string &path)
Read a layered model from either interchange: the LINE model.json or the LQNS .lqnx.
lqn::LqnModel< T > build_lqn_model_from_json(const detail::json &root)
Build the intermediate LqnModel<T> from a parsed model.json envelope carrying a LayeredNetwork.
lqn::LqnStruct< T > build_lqn_from_json(const detail::json &root)
Build an LqnStruct<T> from a parsed model.json envelope carrying a LayeredNetwork.
bool is_layered_json_text(const std::string &text)
True when a DOCUMENT ALREADY IN MEMORY is a LayeredNetwork model.json.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
PrecedenceType
Activity precedence kinds, with the values of MATLAB ActivityPrecedenceType.
Definition lang_types.h:472
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
Definition lqn_reader.h:444
LqnStruct< T > read_lqnx(const std::string &path)
Read a .lqnx model.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Reader for the LINE model.json interchange (a Network model) into a qn::Network<T> built through the ...
static Distrib immediate()
The Immediate singleton.
Definition lang_types.h:977
static Distrib exp_mean(const T &m)
Definition lang_types.h:930
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:409
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:413
std::vector< detail::RawProc > procs
Definition lqn_reader.h:412
std::string name
LayeredNetwork.getName(); empty when unnamed.
Definition lqn_reader.h:411