5#ifndef LINE_IO_LINEMODEL_WRITER_H
6#define LINE_IO_LINEMODEL_WRITER_H
71double to_d(
const T& x) {
72 return num_traits<T>::to_double(x);
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));
83inline json lqn_mult_to_json(
double m) {
84 if (!std::isfinite(m))
return json(2147483647LL);
85 return count_to_json(m);
90bool lqn_dist_declared(
const lang::Distrib<T>& d) {
108 throw UnsupportedError(ErrorKind{
"Export"},
"linemodel_writer: a precedence carries a type with no wire spelling");
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()) +
131 ops.push_back(cols[c]);
134 if (ops.empty())
continue;
136 row[
"operands"] = ops;
138 row[
"cap"] = to_d(b->at(r));
142 for (
const lqn::detail::RawLinConRow<T>& nr : named) {
144 row[
"operands"] = nr.names;
145 json cf = json::array();
146 for (
const T& c : nr.coeffs) cf.push_back(to_d(c));
148 row[
"cap"] = to_d(nr.cap);
155inline void lqn_refuse(
const std::string& elem,
const std::string& what) {
157 ", which no model.json reader (MATLAB, JAR, Python or C++) rebuilds; "
158 "refusing rather than writing a document that reloads as a "
163void lqn_refuse_dependence(
const std::string& elem,
const std::vector<T>& lld,
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");
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) {
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;
191json lqn_precedence_to_json(
const lqn::detail::RawPrecedence<T>& p,
const std::string& 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;
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;
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) {
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]);
232 }
else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_CACHE) {
233 pj[
"type"] =
"CacheAccess";
234 pj[
"activities"] = both;
236 lqn_refuse(
"task '" + task +
"'",
"a precedence of a pre/post type pair with no typed form");
249 out[
"type"] =
"LayeredNetwork";
250 out[
"name"] = m.
name.empty() ? std::string(
"LQN") : m.
name;
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);
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 +
"'";
262 detail::lqn_refuse(who,
"a queue-dependent service scaling");
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;
273 const json rows = detail::lincon_to_json<T>(
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;
279 if (pl != m.
proc_pools.end() && !pl->second.empty())
280 h[
"serverTypes"] = detail::lqn_pools_to_json<T>(pl->second);
283 out[
"hosts"] = hosts;
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)");
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);
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);
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);
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);
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";
331 out[
"tasks"] = tasks;
333 json entries = json::array();
334 std::map<std::string, std::string> reply_of;
335 for (
const lqn::detail::RawEntry<T>& e : m.
entries) {
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) {
344 f[
"dest"] = e.fwd_dest[k];
345 f[
"prob"] = k < e.fwd_prob.size() ? to_d(e.fwd_prob[k]) : 1.0;
348 ej[
"forwarding"] = fw;
350 if (e.cardinality > 0) {
351 ej[
"entryType"] =
"ItemEntry";
352 ej[
"totalItems"] =
static_cast<long long>(e.cardinality);
353 if (!e.popularity.empty()) {
355 for (std::size_t k = 1; k <= e.popularity.size(); ++k)
361 for (
const std::string& a : e.reply_activities) reply_of[a] = e.name;
362 entries.push_back(ej);
364 out[
"entries"] = entries;
366 json acts = json::array();
367 for (
const lqn::detail::RawActivity<T>& a : m.
acts) {
368 const std::string who =
"activity '" + 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;
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]) {
387 cj[
"mean"] = to_d(c.mean);
393 if (!a.call_groups.empty()) {
394 json groups = json::array();
395 for (std::size_t g = 0; g < a.call_groups.size(); ++g) {
399 " declares a call group with routing strategy '" +
401 "'; call groups carry RROBIN or JSQ");
404 gj[
"dests"] = a.call_groups[g].second;
405 groups.push_back(gj);
407 aj[
"callGroups"] = groups;
411 out[
"activities"] = acts;
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;
426 out[
"type"] =
"Workflow";
427 out[
"name"] =
wf.name();
428 json acts = json::array();
431 aj[
"name"] = a.name();
432 aj[
"hostDemand"] = detail::dist_to_json(a.host_demand());
435 out[
"activities"] = acts;
436 json precs = json::array();
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);
447 out[
"precedences"] = precs;
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);
468 json stages = json::array();
469 for (std::size_t s = 0; s < E; ++s) {
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");
477 sj[
"name"] = st.
name;
478 if (!st.
type.empty()) sj[
"type"] = st.
type;
480 stages.push_back(sj);
482 out[
"stages"] = stages;
485 std::vector<bool> failure_stage(E,
false);
488 if (d < E) failure_stage[d] =
true;
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) {
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";
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);
508 out[
"transitions"] = trans;
510 json fails = json::array();
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";
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";
529 nj[
"repairResetPolicy"] = nf.repair_reset;
533 if (!fails.empty()) out[
"nodeFailures"] = fails;
540 root[
"format"] =
"line-model";
541 root[
"version"] =
"1.0";
542 root[
"model"] = model;
543 detail::wire_nonfinite(root);
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";
UnsupportedError(const std::string &what)
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.
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.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A class-dependent scaling map, sn.cdscaling.
const char * routing_to_text(RoutingStrategy r)
std::string elem(const LqnStruct< T > &lqn, std::size_t idx)
Conservation laws of a layered queueing network, enumerated from its structure.
qn::NetworkStruct -> model.json, the inverse of network_reader.h.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
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
The intermediate model, and the second stage that flattens it.
std::vector< detail::RawTask< T > > tasks
std::vector< detail::RawActivity< T > > acts
std::map< std::size_t, std::vector< detail::RawServerPool< T > > > proc_pools
std::vector< detail::RawProc > procs
std::map< std::size_t, CdScaling< T > > proc_jdscaling
std::map< std::size_t, std::pair< Matrix< T >, std::vector< T > > > proc_lincon
std::map< std::size_t, std::vector< detail::RawLinConRow< T > > > proc_linconrows
Admission constraints declared on a HOST, by 0-based processor slot.
std::map< std::size_t, CdScaling< T > > proc_cdscaling
std::string name
LayeredNetwork.getName(); empty when unnamed.
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.
std::vector< detail::RawEntry< T > > entries
One precedence of the activity graph.
An activity workflow reduced to one phase-type law.