5#ifndef LINE_IO_LQN_JSON_READER_H
6#define LINE_IO_LQN_JSON_READER_H
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 +
"'");
79inline double lqn_mult_from_json(
const json& v) {
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());
85 const double m = v.get<
double>();
86 return m >= 2147483647.0 ? std::numeric_limits<double>::infinity() : m;
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;
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;
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");
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>());
118 for (
const json& o : row.at(
"operands")) ops.push_back(o.get<std::string>());
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>()));
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>()));
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");
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>();
149 return (qd > 0 && qd <
double(nbranches)) ? std::size_t(qd + 0.5) : 0;
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")
166 "' is not a LayeredNetwork; a Network model is read by "
174 const json empty = json::array();
175 const json& hosts = model.contains(
"hosts")
177 : (model.contains(
"processors") ? model.at(
"processors") : empty);
178 std::vector<std::pair<double, double>> quantum_speed;
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>();
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"))
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")));
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;
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>();
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"))
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> >();
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"))),
224 b.
task(name, mult, sched, on, repl);
227 if (t.contains(
"priority")) b.
task_priority(name,
static_cast<int>(t.at(
"priority").get<
double>()));
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>())));
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");
256 has_setup_obj ? detail::dist_from_json<T>(t.at(
"setupTime"))
258 t.at(
"setupTimeMean").get<
double>()));
260 has_off_obj ? detail::dist_from_json<T>(t.at(
"delayOffTime"))
262 t.at(
"delayOffTimeMean").get<
double>()));
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>();
277 if (e.contains(
"accessProb"))
278 pop = detail::pmf_from_json<T>(e.at(
"accessProb"), card);
285 if (e.contains(
"arrival"))
286 b.
open_arrival(name, detail::dist_from_json<T>(e.at(
"arrival")));
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>();
296 ? detail::dist_from_json<T>(a.at(
"hostDemand"))
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])) {
311 const std::string dest = c.contains(
"dest")
312 ? c.at(
"dest").get<std::string>()
313 : c.at(
"entry").get<std::string>();
315 if (which == 0) b.
sync_call(name, dest, mean);
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>(),
337 if (model.contains(
"precedences")) {
338 for (
const json& p : model.at(
"precedences")) {
339 const std::string kind = p.value(
"type", std::string());
343 const P pre = detail::lqn_prectype_from_json(p.value(
"preType", std::string(
"pre")),
true);
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"))
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) {
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) {
362 }
else if (pre == P::PRE_SEQ && post == P::POST_OR) {
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;
370 b.
or_fork(pres.at(0), posts, probs);
371 }
else if (pre == P::PRE_SEQ && post == P::POST_LOOP) {
375 if (posts.size() < 2)
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");
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");
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");
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") {
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()),
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") {
416 if (!p.contains(
"preActivity"))
418 "lqn_json_reader: a Loop precedence carries its trigger as 'preActivity'");
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(),
426 }
else if (kind ==
"CacheAccess") {
429 "lqn_json_reader: a CacheAccess precedence names the read activity plus "
430 "its hit and miss branches");
434 "' has no builder counterpart");
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);
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;
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];
449 if (model.contains(
"name") && model.at(
"name").is_string()) m.
name = model.at(
"name").get<std::string>();
473 while (i < text.size() && std::isspace(
static_cast<unsigned char>(text[i]))) ++i;
474 if (i >= text.size() || text[i] !=
'{')
return false;
477 root = detail::json::parse(text);
478 }
catch (
const detail::json::parse_error&) {
481 const detail::json& model = root.contains(
"model") ? root.at(
"model") : root;
482 return model.value(
"type", std::string()) ==
"LayeredNetwork";
494 std::ifstream in(path.c_str());
495 if (!in)
return false;
497 while (in.get(c) && std::isspace(
static_cast<unsigned char>(c))) {}
498 if (c !=
'{')
return false;
503 }
catch (
const detail::json::parse_error&) {
506 const detail::json& model = root.contains(
"model") ? root.at(
"model") : root;
507 return model.value(
"type", std::string()) ==
"LayeredNetwork";
513 std::ifstream in(path.c_str());
514 if (!in)
throw InputError(
"lqn_json_reader: cannot open " + path);
518 }
catch (
const detail::json::parse_error& e) {
519 throw InputError(
"lqn_json_reader: malformed JSON in " + path +
": " + e.what());
527 std::ifstream in(path.c_str());
528 if (!in)
throw InputError(
"lqn_json_reader: cannot open " + path);
532 }
catch (
const detail::json::parse_error& e) {
533 throw InputError(
"lqn_json_reader: malformed JSON in " + path +
": " + e.what());
UnsupportedError(const std::string &what)
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.
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.
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.
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.
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.
PrecedenceType
Activity precedence kinds, with the values of MATLAB ActivityPrecedenceType.
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
LqnStruct< T > read_lqnx(const std::string &path)
Read a .lqnx model.
Conservation laws of a layered queueing network, enumerated from its structure.
Reader for the LINE model.json interchange (a Network model) into a qn::Network<T> built through the ...
static Distrib immediate()
The Immediate singleton.
static Distrib exp_mean(const T &m)
The intermediate model, and the second stage that flattens it.
std::vector< detail::RawTask< T > > tasks
std::vector< detail::RawProc > procs
std::string name
LayeredNetwork.getName(); empty when unnamed.