LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
linemodel_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_IO_LINEMODEL_WRITER_H
6#define LINE_IO_LINEMODEL_WRITER_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * model.json writers for a LayeredNetwork, a Workflow and an Environment: the
12 * `layered2json`, `workflow2json` and `environment2json` branches of
13 * `linemodel_save.m`, and the inverses of `lqn_json_reader.h`,
14 * `workflow_reader.h` and `environment_reader.h`. A Network is written by
15 * `network_writer.h`, which the Environment writer reuses for every stage.
16 *
17 * WHICH SPELLING. The four codebases' writers disagree on a few optional keys,
18 * and every reader accepts the union. Where they disagree this writer takes the
19 * form every reader decodes to the same value:
20 *
21 * host multiplicity/scheduling/quantum/speedFactor always written (Python, JAR);
22 * MATLAB omits the defaults, and the JAR and native readers default them differently
23 * task multiplicity/scheduling, call `mean` always written (Python, JAR)
24 * task think time `thinkTime` plus `thinkTimeMean`/`thinkTimeSCV` (Python, JAR)
25 * activity `hostDemand` always written, an Immediate as `{"type":"Immediate"}` (Python, JAR)
26 * ItemEntry `accessProb` a DiscreteSampler object over x = 1..n; the JAR reads only
27 * the object form, and Python's bare array is unreadable there
28 * entry `forwarding`, activity `thinkTime` JAR keys, read by the JAR and C++ readers
29 * precedences the TYPED form (`type` + `activities`), as MATLAB and Python write it
30 * Loop `loopCount` written whenever the count is declared, as MATLAB does
31 * AND-join quorum `preParams: [q]`, only when a quorum is set, as MATLAB does
32 * task `priority`, activity `callOrder` written when not the default (0, STOCHASTIC), as the JAR does
33 *
34 * WHAT IS DROPPED. An entry's `type` (PH1PH2/NONE) and a host declared as `Host` rather than
35 * `Processor` have no key in any codebase or in the schema; no solver reads either, so the
36 * document reloads as the same model and they are left out rather than refused.
37 *
38 * WHAT IS REFUSED. An `LqnModel` field with no wire key in ANY codebase's
39 * reader is refused by name rather than dropped, because a document that
40 * reloads as a different model is worse than no document: a call group
41 * (RROBIN/JSQ), delayed-hit retrieval, the
42 * load/class/joint dependence tables. An Environment stage
43 * holding a LayeredNetwork is refused too, since its `LqnStruct` cannot be
44 * turned back into the declared model and no reference writer serializes it.
45 *
46 * KEY ORDER is alphabetical, since `nlohmann::json` keeps objects sorted, where
47 * the MATLAB and Python writers keep insertion order. JSON objects are unordered
48 * and every reader looks keys up by name, so the order carries no meaning.
49 */
50
51#include <cmath>
52#include <fstream>
53#include <iostream>
54#include <map>
55#include <string>
56#include <vector>
57
63#include "line/util/error.h"
64
65namespace line {
66namespace io {
67
68namespace detail {
69
70template <class T>
71double to_d(const T& x) {
72 return num_traits<T>::to_double(x);
73}
74
75/** A count as a JSON integer when it is one, as the reference writers emit it. */
76inline json count_to_json(double v) {
77 if (std::isfinite(v) && v == std::floor(v) && std::fabs(v) < 9.0e15)
78 return json(static_cast<long long>(v));
79 return json(v);
80}
81
82/** `inf_multiplicity()`: an infinite multiplicity is Java's Integer.MAX_VALUE on the wire. */
83inline json lqn_mult_to_json(double m) {
84 if (!std::isfinite(m)) return json(2147483647LL);
85 return count_to_json(m);
86}
87
88/** A distribution that is really there: neither unset, immediate nor of zero mean. */
89template <class T>
90bool lqn_dist_declared(const lang::Distrib<T>& d) {
91 return d.type != lang::ProcessType::DISABLED && d.type != lang::ProcessType::IMMEDIATE &&
92 to_d(d.mean) > lang::GlobalConstants::FineTol;
93}
94
95/** `prectype_to_str`: the JAR-compatible precedence spelling of the Workflow wire. */
96inline const char* prectype_to_str(lang::PrecedenceType t) {
97 switch (t) {
98 case lang::PrecedenceType::PRE_SEQ: return "pre";
99 case lang::PrecedenceType::PRE_AND: return "pre-AND";
100 case lang::PrecedenceType::PRE_OR: return "pre-OR";
101 case lang::PrecedenceType::POST_SEQ: return "post";
102 case lang::PrecedenceType::POST_AND: return "post-AND";
103 case lang::PrecedenceType::POST_OR: return "post-OR";
104 case lang::PrecedenceType::POST_LOOP: return "post-LOOP";
105 case lang::PrecedenceType::POST_CACHE: return "post-CACHE";
106 default: break;
107 }
108 throw UnsupportedError(ErrorKind{"Export"}, "linemodel_writer: a precedence carries a type with no wire spelling");
109}
110
111/**
112 * `lincon2json`: admission-constraint rows in the NAMED form. A positional (A, b)
113 * is resolved against `cols`, the element's operands in declaration order.
114 */
115template <class T>
116json lincon_to_json(const std::vector<lqn::detail::RawLinConRow<T>>& named, const Matrix<T>* A,
117 const std::vector<T>* b, const std::vector<std::string>& cols,
118 const std::string& elem) {
119 json rows = json::array();
120 if (A != NULL && !A->empty()) {
121 for (std::size_t r = 0; r < A->rows(); ++r) {
122 json ops = json::array(), cf = json::array();
123 for (std::size_t c = 0; c < A->cols(); ++c) {
124 const double a = to_d((*A)(r, c));
125 if (a == 0.0) continue;
126 if (c >= cols.size())
127 throw InputError(ErrorKind{"Model"}, "linemodel_save: admission constraint on " + elem +
128 " references column " + std::to_string(c + 1) +
129 " but the element has only " + std::to_string(cols.size()) +
130 " operands");
131 ops.push_back(cols[c]);
132 cf.push_back(a);
133 }
134 if (ops.empty()) continue;
135 json row;
136 row["operands"] = ops;
137 row["coeffs"] = cf;
138 row["cap"] = to_d(b->at(r));
139 rows.push_back(row);
140 }
141 }
142 for (const lqn::detail::RawLinConRow<T>& nr : named) {
143 json row;
144 row["operands"] = nr.names;
145 json cf = json::array();
146 for (const T& c : nr.coeffs) cf.push_back(to_d(c));
147 row["coeffs"] = cf;
148 row["cap"] = to_d(nr.cap);
149 rows.push_back(row);
150 }
151 return rows;
152}
153
154/** Refuse, by name, a declared LqnModel feature no reader on the wire can rebuild. */
155inline void lqn_refuse(const std::string& elem, const std::string& what) {
156 throw UnsupportedError(ErrorKind{"Export"}, "linemodel_save: " + elem + " declares " + what +
157 ", which no model.json reader (MATLAB, JAR, Python or C++) rebuilds; "
158 "refusing rather than writing a document that reloads as a "
159 "different model");
160}
161
162template <class T>
163void lqn_refuse_dependence(const std::string& elem, const std::vector<T>& lld,
164 const lang::CdScaling<T>& cd, const lang::CdScaling<T>& jd) {
165 if (!lld.empty()) lqn_refuse(elem, "a load-dependent service scaling");
166 if (cd) lqn_refuse(elem, "a class-dependent service scaling");
167 if (jd) lqn_refuse(elem, "a joint-dependent service scaling");
168}
169
170/**
171 * `serverTypes`: the heterogeneous pools of a host or task, one object per pool
172 * in declaration order, operands BY NAME (tasks of a host, entries of a task)
173 * as `admissionConstraints` carries them. `rate` is written only when not 1.
174 */
175template <class T>
176json lqn_pools_to_json(const std::vector<lqn::detail::RawServerPool<T>>& pools) {
177 json arr = json::array();
178 for (const lqn::detail::RawServerPool<T>& sp : pools) {
179 json pj;
180 pj["name"] = sp.name;
181 pj["count"] = count_to_json(sp.count);
182 if (to_d(sp.rate) != 1.0) pj["rate"] = to_d(sp.rate);
183 pj["operands"] = sp.compatible;
184 arr.push_back(pj);
185 }
186 return arr;
187}
188
189/** One typed-form precedence, following `layered2json`. */
190template <class T>
191json lqn_precedence_to_json(const lqn::detail::RawPrecedence<T>& p, const std::string& task) {
192 typedef lang::PrecedenceType P;
193 json pj;
194 pj["task"] = task;
195 std::vector<std::string> both(p.preacts);
196 both.insert(both.end(), p.postacts.begin(), p.postacts.end());
197 if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_SEQ) {
198 pj["type"] = "Serial";
199 pj["activities"] = both;
200 } else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_AND) {
201 pj["type"] = "AndFork";
202 pj["activities"] = both;
203 } else if (p.pretype == P::PRE_AND && p.posttype == P::POST_SEQ) {
204 pj["type"] = "AndJoin";
205 pj["activities"] = both;
206 // the quorum travels as a one-element preParams, as MATLAB linemodel_save writes it
207 if (p.has_quorum) pj["preParams"] = json::array({p.quorum});
208 } else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_OR) {
209 pj["type"] = "OrFork";
210 pj["activities"] = both;
211 if (!p.postparams.empty()) {
212 json pr = json::array();
213 for (const T& x : p.postparams) pr.push_back(to_d(x));
214 pj["probabilities"] = pr;
215 }
216 } else if (p.pretype == P::PRE_OR && p.posttype == P::POST_SEQ) {
217 if (!p.preparams.empty()) lqn_refuse("task '" + task + "'", "OR-join branch probabilities");
218 pj["type"] = "OrJoin";
219 pj["activities"] = both;
220 } else if (p.posttype == P::POST_LOOP) {
221 // postacts is the body followed by the exit; the trigger travels as preActivity
222 pj["type"] = "Loop";
223 pj["activities"] = p.postacts;
224 if (!p.preacts.empty()) pj["preActivity"] = p.preacts[0];
225 if (!p.postparams.empty()) {
226 for (const T& c : p.postparams)
227 if (to_d(c) != to_d(p.postparams[0]))
228 lqn_refuse("task '" + task + "'", "a loop whose body activities repeat "
229 "different numbers of times");
230 pj["loopCount"] = to_d(p.postparams[0]);
231 }
232 } else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_CACHE) {
233 pj["type"] = "CacheAccess";
234 pj["activities"] = both;
235 } else {
236 lqn_refuse("task '" + task + "'", "a precedence of a pre/post type pair with no typed form");
237 }
238 return pj;
239}
240
241} // namespace detail
242
243/** `layered2json`: the `model` object of a LayeredNetwork document. */
244template <class T>
245detail::json lqn_model_to_json(const lqn::LqnModel<T>& m) {
246 using detail::json;
247 using detail::to_d;
248 json out;
249 out["type"] = "LayeredNetwork";
250 out["name"] = m.name.empty() ? std::string("LQN") : m.name;
251
252 // Operands of a host constraint are its tasks, of a task constraint its entries.
253 std::vector<std::vector<std::string>> tasks_of(m.procs.size()), entries_of(m.tasks.size());
254 for (const auto& t : m.tasks) tasks_of.at(t.proc_slot).push_back(t.name);
255 for (const auto& e : m.entries) entries_of.at(e.task_slot).push_back(e.name);
256
257 json hosts = json::array();
258 for (std::size_t i = 0; i < m.procs.size(); ++i) {
259 const lqn::detail::RawProc& p = m.procs[i];
260 const std::string who = "host '" + p.name + "'";
261 if (m.proc_lldscaling.count(i) || m.proc_cdscaling.count(i) || m.proc_jdscaling.count(i))
262 detail::lqn_refuse(who, "a queue-dependent service scaling");
263 json h;
264 h["name"] = p.name;
265 h["multiplicity"] = detail::lqn_mult_to_json(p.mult);
266 h["scheduling"] = detail::sched_to_json(p.sched);
267 h["quantum"] = p.quantum;
268 h["speedFactor"] = p.speed_factor;
269 if (p.repl > 1) h["replication"] = detail::count_to_json(p.repl);
270 static const std::vector<lqn::detail::RawLinConRow<T>> kNone;
271 const auto named = m.proc_linconrows.find(i);
272 const auto pos = m.proc_lincon.find(i);
273 const json rows = detail::lincon_to_json<T>(
274 named == m.proc_linconrows.end() ? kNone : named->second,
275 pos == m.proc_lincon.end() ? NULL : &pos->second.first,
276 pos == m.proc_lincon.end() ? NULL : &pos->second.second, tasks_of[i], p.name);
277 if (!rows.empty()) h["admissionConstraints"] = rows;
278 const auto pl = m.proc_pools.find(i);
279 if (pl != m.proc_pools.end() && !pl->second.empty())
280 h["serverTypes"] = detail::lqn_pools_to_json<T>(pl->second);
281 hosts.push_back(h);
282 }
283 out["hosts"] = hosts;
284
285 json tasks = json::array();
286 for (std::size_t i = 0; i < m.tasks.size(); ++i) {
287 const lqn::detail::RawTask<T>& t = m.tasks[i];
288 const std::string who = "task '" + t.name + "'";
289 detail::lqn_refuse_dependence(who, t.lldscaling, t.cdscaling, t.jdscaling);
290 if (t.retrieval) detail::lqn_refuse(who, "delayed-hit retrieval (setRetrieval)");
291 json tj;
292 tj["name"] = t.name;
293 tj["host"] = m.procs.at(t.proc_slot).name;
294 tj["multiplicity"] = detail::lqn_mult_to_json(t.mult);
295 tj["scheduling"] = detail::sched_to_json(t.sched);
296 if (t.repl > 1) tj["replication"] = detail::count_to_json(t.repl);
297 if (t.priority != 0) tj["priority"] = t.priority;
298 if (detail::lqn_dist_declared(t.thinktime)) {
299 tj["thinkTime"] = detail::dist_to_json(t.thinktime);
300 tj["thinkTimeMean"] = to_d(t.thinktime.mean);
301 tj["thinkTimeSCV"] = to_d(t.thinktime.scv);
302 }
303 if (!t.fanin.empty()) {
304 json fi = json::object();
305 for (const auto& kv : t.fanin) fi[kv.first] = detail::count_to_json(kv.second);
306 tj["fanIn"] = fi;
307 }
308 if (!t.fanout.empty()) {
309 json fo = json::object();
310 for (const auto& kv : t.fanout) fo[kv.first] = detail::count_to_json(kv.second);
311 tj["fanOut"] = fo;
312 }
313 const json rows = detail::lincon_to_json<T>(t.linconrows, &t.lincon_A, &t.lincon_b,
314 entries_of[i], t.name);
315 if (!rows.empty()) tj["admissionConstraints"] = rows;
316 if (!t.pools.empty()) tj["serverTypes"] = detail::lqn_pools_to_json<T>(t.pools);
317 const bool has_setup = detail::lqn_dist_declared(t.setuptime);
318 if (has_setup) tj["setupTime"] = detail::dist_to_json(t.setuptime);
319 if (detail::lqn_dist_declared(t.delayofftime))
320 tj["delayOffTime"] = detail::dist_to_json(t.delayofftime);
321 if (t.nitems > 0) {
322 tj["taskType"] = "CacheTask";
323 tj["totalItems"] = static_cast<long long>(t.nitems);
324 tj["cacheCapacity"] = t.itemcap;
325 tj["replacementStrategy"] = detail::replacement_to_json(t.replacestrat);
326 } else if (has_setup) {
327 tj["taskType"] = "SetupTask";
328 }
329 tasks.push_back(tj);
330 }
331 out["tasks"] = tasks;
332
333 json entries = json::array();
334 std::map<std::string, std::string> reply_of; // activity -> entry it replies to
335 for (const lqn::detail::RawEntry<T>& e : m.entries) {
336 json ej;
337 ej["name"] = e.name;
338 ej["task"] = m.tasks.at(e.task_slot).name;
339 if (e.has_arrival) ej["arrival"] = detail::dist_to_json(e.arrival);
340 if (!e.fwd_dest.empty()) {
341 json fw = json::array();
342 for (std::size_t k = 0; k < e.fwd_dest.size(); ++k) {
343 json f;
344 f["dest"] = e.fwd_dest[k];
345 f["prob"] = k < e.fwd_prob.size() ? to_d(e.fwd_prob[k]) : 1.0;
346 fw.push_back(f);
347 }
348 ej["forwarding"] = fw;
349 }
350 if (e.cardinality > 0) {
351 ej["entryType"] = "ItemEntry";
352 ej["totalItems"] = static_cast<long long>(e.cardinality);
353 if (!e.popularity.empty()) {
354 std::vector<T> x;
355 for (std::size_t k = 1; k <= e.popularity.size(); ++k)
356 x.push_back(num_traits<T>::from_int(static_cast<long>(k)));
357 ej["accessProb"] =
358 detail::dist_to_json(lang::Distrib<T>::discrete_sampler(e.popularity, x));
359 }
360 }
361 for (const std::string& a : e.reply_activities) reply_of[a] = e.name;
362 entries.push_back(ej);
363 }
364 out["entries"] = entries;
365
366 json acts = json::array();
367 for (const lqn::detail::RawActivity<T>& a : m.acts) {
368 const std::string who = "activity '" + a.name + "'";
369 json aj;
370 aj["name"] = a.name;
371 aj["task"] = m.tasks.at(a.task_slot).name;
372 aj["hostDemand"] = detail::dist_to_json(a.hostdem);
373 if (!a.bound_to_entry.empty()) aj["boundToEntry"] = a.bound_to_entry;
374 const auto r = reply_of.find(a.name);
375 if (r != reply_of.end()) aj["repliesTo"] = r->second;
376 if (detail::lqn_dist_declared(a.thinktime)) aj["thinkTime"] = detail::dist_to_json(a.thinktime);
377 if (a.phase != 1) aj["phase"] = a.phase; // post-reply phase; omitted at the default 1
378 if (a.call_order != "STOCHASTIC") aj["callOrder"] = a.call_order;
379 const char* kKey[2] = {"synchCalls", "asynchCalls"};
380 const std::vector<lqn::detail::RawCall<T>>* calls[2] = {&a.sync_calls, &a.async_calls};
381 for (int w = 0; w < 2; ++w) {
382 if (calls[w]->empty()) continue;
383 json arr = json::array();
384 for (const lqn::detail::RawCall<T>& c : *calls[w]) {
385 json cj;
386 cj["dest"] = c.dest;
387 cj["mean"] = to_d(c.mean);
388 arr.push_back(cj);
389 }
390 aj[kKey[w]] = arr;
391 }
392 // Routed call groups: which of the synchCalls above one dispatcher issues (the .lqnx <call-group>)
393 if (!a.call_groups.empty()) {
394 json groups = json::array();
395 for (std::size_t g = 0; g < a.call_groups.size(); ++g) {
396 const lang::RoutingStrategy rs = a.call_groups[g].first;
398 throw UnsupportedError(ErrorKind{"Export"}, "linemodel_save: " + who +
399 " declares a call group with routing strategy '" +
400 std::string(lang::routing_to_text(rs)) +
401 "'; call groups carry RROBIN or JSQ");
402 json gj;
403 gj["strategy"] = rs == lang::RoutingStrategy::RROBIN ? "RROBIN" : "JSQ";
404 gj["dests"] = a.call_groups[g].second;
405 groups.push_back(gj);
406 }
407 aj["callGroups"] = groups;
408 }
409 acts.push_back(aj);
410 }
411 out["activities"] = acts;
412
413 json precs = json::array();
414 for (const lqn::detail::RawTask<T>& t : m.tasks)
415 for (const lqn::detail::RawPrecedence<T>& p : t.precedences)
416 precs.push_back(detail::lqn_precedence_to_json(p, t.name));
417 if (!precs.empty()) out["precedences"] = precs;
418 return out;
419}
420
421/** `workflow2json`: the `model` object of a Workflow document. */
422template <class T>
424 using detail::json;
425 json out;
426 out["type"] = "Workflow";
427 out["name"] = wf.name();
428 json acts = json::array();
429 for (const workflow::WorkflowActivity<T>& a : wf.activities()) {
430 json aj;
431 aj["name"] = a.name();
432 aj["hostDemand"] = detail::dist_to_json(a.host_demand());
433 acts.push_back(aj);
434 }
435 out["activities"] = acts;
436 json precs = json::array();
437 for (const workflow::Precedence<T>& p : wf.precedences()) {
438 json pj;
439 pj["preActs"] = p.pre_acts;
440 pj["postActs"] = p.post_acts;
441 pj["preType"] = detail::prectype_to_str(p.pre_type);
442 pj["postType"] = detail::prectype_to_str(p.post_type);
443 if (!p.pre_params.empty()) pj["preParams"] = detail::vec_to_json(p.pre_params);
444 if (!p.post_params.empty()) pj["postParams"] = detail::vec_to_json(p.post_params);
445 precs.push_back(pj);
446 }
447 out["precedences"] = precs;
448 return out;
449}
450
451/**
452 * `environment2json`: the `model` object of an Environment document.
453 *
454 * The stages go out EXPANDED, each with its own network, plus the `nodeFailures`
455 * descriptors that alone carry the reset policies, as both reference writers do.
456 * A reset FUNCTION on an ordinary arc has no wire form in any codebase; it is
457 * dropped with a warning, as MATLAB warns for a custom node-failure policy.
458 */
459template <class T>
461 using detail::json;
462 json out;
463 out["type"] = "Environment";
464 out["name"] = e.name();
465 const std::size_t E = e.nstages();
466 out["numStages"] = static_cast<long long>(E);
467
468 json stages = json::array();
469 for (std::size_t s = 0; s < E; ++s) {
470 const env::EnvStage<T>& st = e.stage(s);
471 if (st.has_lqn)
472 throw UnsupportedError(ErrorKind{"Export"},
473 "linemodel_save: stage '" + st.name +
474 "' holds a LayeredNetwork, whose finalized LqnStruct cannot be written back as "
475 "the declared model; no reference writer serializes a layered stage either");
476 json sj;
477 sj["name"] = st.name;
478 if (!st.type.empty()) sj["type"] = st.type;
479 if (st.has_model) sj["model"] = network_to_json(st.model);
480 stages.push_back(sj);
481 }
482 out["stages"] = stages;
483
484 // Arcs into or out of a DOWN_<node> stage are a node failure's, whose reset travels in nodeFailures.
485 std::vector<bool> failure_stage(E, false);
486 for (const env::NodeFailure<T>& nf : e.node_failures()) {
487 const std::size_t d = e.find_stage(env::Environment<T>::down_stage_name(nf.node));
488 if (d < E) failure_stage[d] = true;
489 }
490 json trans = json::array();
491 for (std::size_t a = 0; a < E; ++a)
492 for (std::size_t b = 0; b < E; ++b) {
493 const env::EnvArc<T>& arc = e.arc(a, b);
494 if (!arc.enabled || arc.dist.type == lang::ProcessType::DISABLED) continue;
495 if ((arc.reset || arc.reset_rates) && !failure_stage[a] && !failure_stage[b])
496 std::cerr << "[LINE] Warning: linemodel_save: the transition " << e.stage(a).name
497 << " -> " << e.stage(b).name
498 << " carries a reset function, which cannot be serialized to JSON; the "
499 "saved model reloads without it.\n";
500 json tj;
501 tj["from"] = static_cast<long long>(a);
502 tj["to"] = static_cast<long long>(b);
503 tj["distribution"] = detail::dist_to_json(arc.dist);
504 if (e.is_kernel_stage(a))
505 tj["probability"] = e.kernel_prob(a, b); // kernel arc (add_kernel_transition)
506 trans.push_back(tj);
507 }
508 out["transitions"] = trans;
509
510 json fails = json::array();
511 for (const env::NodeFailure<T>& nf : e.node_failures()) {
512 json nj;
513 nj["node"] = nf.node;
514 nj["breakdownRate"] = detail::dist_to_json(nf.breakdown);
515 if (nf.has_repair) nj["repairRate"] = detail::dist_to_json(nf.repair);
516 nj["downService"] = detail::dist_to_json(nf.down_service);
517 if (nf.breakdown_reset == "custom")
518 std::cerr << "[LINE] Warning: linemodel_save: node failure on \"" << nf.node
519 << "\" uses a custom breakdown reset function, which cannot be serialized "
520 "to JSON; the saved model falls back to the 'keep' policy on reload.\n";
521 else
522 nj["breakdownResetPolicy"] = nf.breakdown_reset;
523 if (!nf.repair_reset.empty()) {
524 if (nf.repair_reset == "custom")
525 std::cerr << "[LINE] Warning: linemodel_save: node failure on \"" << nf.node
526 << "\" uses a custom repair reset function, which cannot be serialized "
527 "to JSON; the saved model falls back to the 'keep' policy on reload.\n";
528 else
529 nj["repairResetPolicy"] = nf.repair_reset;
530 }
531 fails.push_back(nj);
532 }
533 if (!fails.empty()) out["nodeFailures"] = fails;
534 return out;
535}
536
537/** The `{format, version, model}` envelope around a `model` object, with the wire's non-finites. */
538inline detail::json linemodel_envelope(const detail::json& model) {
539 detail::json root;
540 root["format"] = "line-model";
541 root["version"] = "1.0";
542 root["model"] = model;
543 detail::wire_nonfinite(root);
544 return root;
545}
546
547/** Write an envelope, indented as `write_network_json` indents it. */
548inline void write_linemodel_json(const detail::json& root, const std::string& path) {
549 std::ofstream out(path.c_str());
550 if (!out) throw InputError(ErrorKind{"File"}, "linemodel_save: cannot open " + path + " for writing");
551 out << root.dump(2) << "\n";
552}
553
554} // namespace io
555} // namespace line
556
557#endif // LINE_IO_LINEMODEL_WRITER_H
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
const std::string & name() const
const std::vector< NodeFailure< T > > & node_failures() const
nodeFailures, the declarative record of the breakdowns declared here.
const EnvStage< T > & stage(std::size_t e) const
double kernel_prob(std::size_t e, std::size_t h) const
kernelProb(e,h): the kernel arc's probability, NaN for a competing arc or none.
std::size_t find_stage(const std::string &nm) const
The index of the stage called nm, or nstages() when there is none.
std::size_t nstages() const
static std::string down_stage_name(const std::string &nm)
The name addNodeBreakdown gives the stage in which nm is down.
bool is_kernel_stage(std::size_t e) const
isKernelStage: true if stage e's arcs are kernel arcs.
const EnvArc< T > & arc(std::size_t e, std::size_t h) const
A computational activity.
Definition workflow.h:102
A random environment: a port of matlab/src/lang/Environment.m, restricted to what SolverENV reads out...
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
detail::json environment_to_json(const env::Environment< T > &e)
environment2json: the model object of an Environment document.
detail::json network_to_json(const qn::NetworkStruct< T > &sn)
qn::NetworkStruct -> the model.json model object.
detail::json lqn_model_to_json(const lqn::LqnModel< T > &m)
layered2json: the model object of a LayeredNetwork document.
detail::json linemodel_envelope(const detail::json &model)
The {format, version, model} envelope around a model object, with the wire's non-finites.
detail::json workflow_to_json(const workflow::Workflow< T > &wf)
workflow2json: the model object of a Workflow document.
void write_linemodel_json(const detail::json &root, const std::string &path)
Write an envelope, indented as write_network_json indents it.
PrecedenceType
Activity precedence kinds, with the values of MATLAB ActivityPrecedenceType.
Definition lang_types.h:477
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
Definition lang_types.h:396
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A class-dependent scaling map, sn.cdscaling.
Definition lang_types.h:736
const char * routing_to_text(RoutingStrategy r)
Definition lang_types.h:409
std::string elem(const LqnStruct< T > &lqn, std::size_t idx)
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
qn::NetworkStruct -> model.json, the inverse of network_reader.h.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
One arc of the environment process.
lang::Distrib< T > dist
the e -> h transition time
ResetMarginal reset
empty means the identity
ResetEnvRates< T > reset_rates
empty means the rate does not depend on the state
One stage: a name, a category, and the model in force while it lasts.
std::string type
the stage category, informational only
qn::NetworkStruct< T > model
Environment.nodeFailures{k}: the declarative record of one node breakdown.
static Distrib discrete_sampler(const std::vector< T > &p, const std::vector< T > &x)
DiscreteSampler(p, x): the pmf p over the points x.
static constexpr double FineTol
Definition lang_types.h:765
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:411
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:415
std::vector< detail::RawActivity< T > > acts
Definition lqn_reader.h:417
std::map< std::size_t, std::vector< detail::RawServerPool< T > > > proc_pools
Definition lqn_reader.h:441
std::vector< detail::RawProc > procs
Definition lqn_reader.h:414
std::map< std::size_t, CdScaling< T > > proc_jdscaling
Definition lqn_reader.h:439
std::map< std::size_t, std::pair< Matrix< T >, std::vector< T > > > proc_lincon
Definition lqn_reader.h:427
std::map< std::size_t, std::vector< detail::RawLinConRow< T > > > proc_linconrows
Admission constraints declared on a HOST, by 0-based processor slot.
Definition lqn_reader.h:426
std::map< std::size_t, CdScaling< T > > proc_cdscaling
Definition lqn_reader.h:437
std::string name
LayeredNetwork.getName(); empty when unnamed.
Definition lqn_reader.h:413
std::map< std::size_t, std::vector< T > > proc_lldscaling
Queue-dependent service rates and compatibility pools declared on a HOST, by 0-based processor slot.
Definition lqn_reader.h:436
std::vector< detail::RawEntry< T > > entries
Definition lqn_reader.h:416
One precedence of the activity graph.
Definition workflow.h:79
An activity workflow reduced to one phase-type law.