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(
"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(
"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");
172json lqn_precedence_to_json(
const lqn::detail::RawPrecedence<T>& p,
const std::string& task) {
176 std::vector<std::string> both(p.preacts);
177 both.insert(both.end(), p.postacts.begin(), p.postacts.end());
178 if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_SEQ) {
179 pj[
"type"] =
"Serial";
180 pj[
"activities"] = both;
181 }
else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_AND) {
182 pj[
"type"] =
"AndFork";
183 pj[
"activities"] = both;
184 }
else if (p.pretype == P::PRE_AND && p.posttype == P::POST_SEQ) {
185 pj[
"type"] =
"AndJoin";
186 pj[
"activities"] = both;
188 if (p.has_quorum) pj[
"preParams"] = json::array({p.quorum});
189 }
else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_OR) {
190 pj[
"type"] =
"OrFork";
191 pj[
"activities"] = both;
192 if (!p.postparams.empty()) {
193 json pr = json::array();
194 for (
const T& x : p.postparams) pr.push_back(to_d(x));
195 pj[
"probabilities"] = pr;
197 }
else if (p.pretype == P::PRE_OR && p.posttype == P::POST_SEQ) {
198 if (!p.preparams.empty()) lqn_refuse(
"task '" + task +
"'",
"OR-join branch probabilities");
199 pj[
"type"] =
"OrJoin";
200 pj[
"activities"] = both;
201 }
else if (p.posttype == P::POST_LOOP) {
204 pj[
"activities"] = p.postacts;
205 if (!p.preacts.empty()) pj[
"preActivity"] = p.preacts[0];
206 if (!p.postparams.empty()) {
207 for (
const T& c : p.postparams)
208 if (to_d(c) != to_d(p.postparams[0]))
209 lqn_refuse(
"task '" + task +
"'",
"a loop whose body activities repeat "
210 "different numbers of times");
211 pj[
"loopCount"] = to_d(p.postparams[0]);
213 }
else if (p.pretype == P::PRE_SEQ && p.posttype == P::POST_CACHE) {
214 pj[
"type"] =
"CacheAccess";
215 pj[
"activities"] = both;
217 lqn_refuse(
"task '" + task +
"'",
"a precedence of a pre/post type pair with no typed form");
230 out[
"type"] =
"LayeredNetwork";
231 out[
"name"] = m.
name.empty() ? std::string(
"LQN") : m.
name;
234 std::vector<std::vector<std::string>> tasks_of(m.
procs.size()), entries_of(m.
tasks.size());
235 for (
const auto& t : m.
tasks) tasks_of.at(t.proc_slot).push_back(t.name);
236 for (
const auto& e : m.
entries) entries_of.at(e.task_slot).push_back(e.name);
238 json hosts = json::array();
239 for (std::size_t i = 0; i < m.
procs.size(); ++i) {
240 const lqn::detail::RawProc& p = m.
procs[i];
241 const std::string who =
"host '" + p.name +
"'";
243 detail::lqn_refuse(who,
"a queue-dependent service scaling");
245 detail::lqn_refuse(who,
"server pools");
248 h[
"multiplicity"] = detail::lqn_mult_to_json(p.mult);
249 h[
"scheduling"] = detail::sched_to_json(p.sched);
250 h[
"quantum"] = p.quantum;
251 h[
"speedFactor"] = p.speed_factor;
252 if (p.repl > 1) h[
"replication"] = detail::count_to_json(p.repl);
253 static const std::vector<lqn::detail::RawLinConRow<T>> kNone;
256 const json rows = detail::lincon_to_json<T>(
258 pos == m.
proc_lincon.end() ? NULL : &pos->second.first,
259 pos == m.
proc_lincon.end() ? NULL : &pos->second.second, tasks_of[i], p.name);
260 if (!rows.empty()) h[
"admissionConstraints"] = rows;
263 out[
"hosts"] = hosts;
265 json tasks = json::array();
266 for (std::size_t i = 0; i < m.
tasks.size(); ++i) {
267 const lqn::detail::RawTask<T>& t = m.
tasks[i];
268 const std::string who =
"task '" + t.name +
"'";
269 detail::lqn_refuse_dependence(who, t.lldscaling, t.cdscaling, t.jdscaling);
270 if (!t.pools.empty()) detail::lqn_refuse(who,
"server pools");
271 if (t.retrieval) detail::lqn_refuse(who,
"delayed-hit retrieval (setRetrieval)");
274 tj[
"host"] = m.
procs.at(t.proc_slot).name;
275 tj[
"multiplicity"] = detail::lqn_mult_to_json(t.mult);
276 tj[
"scheduling"] = detail::sched_to_json(t.sched);
277 if (t.repl > 1) tj[
"replication"] = detail::count_to_json(t.repl);
278 if (t.priority != 0) tj[
"priority"] = t.priority;
279 if (detail::lqn_dist_declared(t.thinktime)) {
280 tj[
"thinkTime"] = detail::dist_to_json(t.thinktime);
281 tj[
"thinkTimeMean"] = to_d(t.thinktime.mean);
282 tj[
"thinkTimeSCV"] = to_d(t.thinktime.scv);
284 if (!t.fanin.empty()) {
285 json fi = json::object();
286 for (
const auto& kv : t.fanin) fi[kv.first] = detail::count_to_json(kv.second);
289 if (!t.fanout.empty()) {
290 json fo = json::object();
291 for (
const auto& kv : t.fanout) fo[kv.first] = detail::count_to_json(kv.second);
294 const json rows = detail::lincon_to_json<T>(t.linconrows, &t.lincon_A, &t.lincon_b,
295 entries_of[i], t.name);
296 if (!rows.empty()) tj[
"admissionConstraints"] = rows;
297 const bool has_setup = detail::lqn_dist_declared(t.setuptime);
298 if (has_setup) tj[
"setupTime"] = detail::dist_to_json(t.setuptime);
299 if (detail::lqn_dist_declared(t.delayofftime))
300 tj[
"delayOffTime"] = detail::dist_to_json(t.delayofftime);
302 tj[
"taskType"] =
"CacheTask";
303 tj[
"totalItems"] =
static_cast<long long>(t.nitems);
304 tj[
"cacheCapacity"] = t.itemcap;
305 tj[
"replacementStrategy"] = detail::replacement_to_json(t.replacestrat);
306 }
else if (has_setup) {
307 tj[
"taskType"] =
"SetupTask";
311 out[
"tasks"] = tasks;
313 json entries = json::array();
314 std::map<std::string, std::string> reply_of;
315 for (
const lqn::detail::RawEntry<T>& e : m.
entries) {
318 ej[
"task"] = m.
tasks.at(e.task_slot).name;
319 if (e.has_arrival) ej[
"arrival"] = detail::dist_to_json(e.arrival);
320 if (!e.fwd_dest.empty()) {
321 json fw = json::array();
322 for (std::size_t k = 0; k < e.fwd_dest.size(); ++k) {
324 f[
"dest"] = e.fwd_dest[k];
325 f[
"prob"] = k < e.fwd_prob.size() ? to_d(e.fwd_prob[k]) : 1.0;
328 ej[
"forwarding"] = fw;
330 if (e.cardinality > 0) {
331 ej[
"entryType"] =
"ItemEntry";
332 ej[
"totalItems"] =
static_cast<long long>(e.cardinality);
333 if (!e.popularity.empty()) {
335 for (std::size_t k = 1; k <= e.popularity.size(); ++k)
341 for (
const std::string& a : e.reply_activities) reply_of[a] = e.name;
342 entries.push_back(ej);
344 out[
"entries"] = entries;
346 json acts = json::array();
347 for (
const lqn::detail::RawActivity<T>& a : m.
acts) {
348 const std::string who =
"activity '" + a.name +
"'";
349 if (a.phase != 1) detail::lqn_refuse(who,
"phase " + std::to_string(a.phase));
350 if (!a.call_groups.empty()) detail::lqn_refuse(who,
"a routed call group (RROBIN/JSQ)");
353 aj[
"task"] = m.
tasks.at(a.task_slot).name;
354 aj[
"hostDemand"] = detail::dist_to_json(a.hostdem);
355 if (!a.bound_to_entry.empty()) aj[
"boundToEntry"] = a.bound_to_entry;
356 const auto r = reply_of.find(a.name);
357 if (r != reply_of.end()) aj[
"repliesTo"] = r->second;
358 if (detail::lqn_dist_declared(a.thinktime)) aj[
"thinkTime"] = detail::dist_to_json(a.thinktime);
359 if (a.call_order !=
"STOCHASTIC") aj[
"callOrder"] = a.call_order;
360 const char* kKey[2] = {
"synchCalls",
"asynchCalls"};
361 const std::vector<lqn::detail::RawCall<T>>* calls[2] = {&a.sync_calls, &a.async_calls};
362 for (
int w = 0; w < 2; ++w) {
363 if (calls[w]->empty())
continue;
364 json arr = json::array();
365 for (
const lqn::detail::RawCall<T>& c : *calls[w]) {
368 cj[
"mean"] = to_d(c.mean);
375 out[
"activities"] = acts;
377 json precs = json::array();
378 for (
const lqn::detail::RawTask<T>& t : m.
tasks)
379 for (
const lqn::detail::RawPrecedence<T>& p : t.precedences)
380 precs.push_back(detail::lqn_precedence_to_json(p, t.name));
381 if (!precs.empty()) out[
"precedences"] = precs;
390 out[
"type"] =
"Workflow";
391 out[
"name"] =
wf.name();
392 json acts = json::array();
395 aj[
"name"] = a.name();
396 aj[
"hostDemand"] = detail::dist_to_json(a.host_demand());
399 out[
"activities"] = acts;
400 json precs = json::array();
403 pj[
"preActs"] = p.pre_acts;
404 pj[
"postActs"] = p.post_acts;
405 pj[
"preType"] = detail::prectype_to_str(p.pre_type);
406 pj[
"postType"] = detail::prectype_to_str(p.post_type);
407 if (!p.pre_params.empty()) pj[
"preParams"] = detail::vec_to_json(p.pre_params);
408 if (!p.post_params.empty()) pj[
"postParams"] = detail::vec_to_json(p.post_params);
411 out[
"precedences"] = precs;
427 out[
"type"] =
"Environment";
428 out[
"name"] = e.
name();
429 const std::size_t E = e.
nstages();
430 out[
"numStages"] =
static_cast<long long>(E);
432 json stages = json::array();
433 for (std::size_t s = 0; s < E; ++s) {
437 "linemodel_save: stage '" + st.
name +
438 "' holds a LayeredNetwork, whose finalized LqnStruct cannot be written back as "
439 "the declared model; no reference writer serializes a layered stage either");
441 sj[
"name"] = st.
name;
442 if (!st.
type.empty()) sj[
"type"] = st.
type;
444 stages.push_back(sj);
446 out[
"stages"] = stages;
449 std::vector<bool> failure_stage(E,
false);
452 if (d < E) failure_stage[d] =
true;
454 json trans = json::array();
455 for (std::size_t a = 0; a < E; ++a)
456 for (std::size_t b = 0; b < E; ++b) {
459 if ((arc.
reset || arc.
reset_rates) && !failure_stage[a] && !failure_stage[b])
460 std::cerr <<
"[LINE] Warning: linemodel_save: the transition " << e.
stage(a).name
461 <<
" -> " << e.
stage(b).name
462 <<
" carries a reset function, which cannot be serialized to JSON; the "
463 "saved model reloads without it.\n";
465 tj[
"from"] =
static_cast<long long>(a);
466 tj[
"to"] =
static_cast<long long>(b);
467 tj[
"distribution"] = detail::dist_to_json(arc.
dist);
470 out[
"transitions"] = trans;
472 json fails = json::array();
475 nj[
"node"] = nf.node;
476 nj[
"breakdownRate"] = detail::dist_to_json(nf.breakdown);
477 if (nf.has_repair) nj[
"repairRate"] = detail::dist_to_json(nf.repair);
478 nj[
"downService"] = detail::dist_to_json(nf.down_service);
479 if (nf.breakdown_reset ==
"custom")
480 std::cerr <<
"[LINE] Warning: linemodel_save: node failure on \"" << nf.node
481 <<
"\" uses a custom breakdown reset function, which cannot be serialized "
482 "to JSON; the saved model falls back to the 'keep' policy on reload.\n";
484 nj[
"breakdownResetPolicy"] = nf.breakdown_reset;
485 if (!nf.repair_reset.empty()) {
486 if (nf.repair_reset ==
"custom")
487 std::cerr <<
"[LINE] Warning: linemodel_save: node failure on \"" << nf.node
488 <<
"\" uses a custom repair reset function, which cannot be serialized "
489 "to JSON; the saved model falls back to the 'keep' policy on reload.\n";
491 nj[
"repairResetPolicy"] = nf.repair_reset;
495 if (!fails.empty()) out[
"nodeFailures"] = fails;
502 root[
"format"] =
"line-model";
503 root[
"version"] =
"1.0";
504 root[
"model"] = model;
505 detail::wire_nonfinite(root);
511 std::ofstream out(path.c_str());
512 if (!out)
throw InputError(
"linemodel_save: cannot open " + path +
" for writing");
513 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
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.
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.
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A class-dependent scaling map, sn.cdscaling.
Conservation laws of a layered queueing network, enumerated from its structure.
qn::NetworkStruct -> model.json, the inverse of network_reader.h.
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.