108const char* kVersion =
"3.0.9";
122bool g_json_output =
false;
137std::vector<std::string>* g_json_sink =
nullptr;
141 explicit InterruptRequested(
const std::string& what) : line::ExternalError(line::ErrorKind{
"Interrupted"}, what) {}
144int (*g_interrupt_cb)(
void*) =
nullptr;
145void* g_interrupt_user =
nullptr;
154void poll_interrupt() {
155 if (g_interrupt_cb !=
nullptr && g_interrupt_cb(g_interrupt_user) != 0)
156 throw InterruptRequested(
"the run was interrupted by the host");
166void emit_document(
const std::string& text) {
167 if (g_json_sink !=
nullptr) g_json_sink->push_back(text);
168 std::printf(
"%s\n", text.c_str());
193 if (j.is_object() || j.is_array()) {
197 if (!j.is_number_float())
return;
198 const double v = j.get<
double>();
199 if (std::isinf(v)) j = (v > 0.0 ?
"Infinity" :
"-Infinity");
209 finitize_document(j);
210 return indent < 0 ? j.dump() : j.dump(indent);
226 double iter_tol = -1.0;
234 long long max_states = -1;
239 std::string multiserver;
244 std::string fork_join;
247 std::size_t samples = 0;
248 unsigned long seed = 0;
249 double cutoff = -1.0;
255 std::vector<std::vector<std::size_t>> cutoff_mat;
257 bool has_cutoff()
const {
return cutoff >= 0.0 || !cutoff_mat.empty(); }
269 std::string fj_tmode;
272 int sync_phases = -1;
275 int mapmsg_quantization = -1;
282 std::string timescale;
289 double mdd_tol = -1.0;
300 std::vector<std::size_t> busy_orders;
301 std::vector<std::size_t> busy_subnet;
305 std::string qrf_params;
306 std::string qrf_alpha;
310 double t0 = 0.0, t1 = -1.0;
311 std::size_t node = 0;
318 std::size_t jobclass = 0;
319 std::vector<long> marg_states;
326 double warmupfrac = -1.0;
336 std::string fluid_boundary;
342 std::string notation;
349 std::string cdf_algorithm;
355 std::string passage_from;
356 std::string passage_into;
358 std::string passage_method;
361 std::size_t passage_orders = 0;
365 std::string passage_taboo;
368 std::string passage_tset;
378 std::string method_perm =
"exact";
386 std::string symbolic;
387 bool equilibria =
false;
391 bool no_interlocking =
false;
394 std::string interlock_method;
395 double interlock_maxpaths = -1.0;
396 std::string interlock_refpath_scope;
397 bool interlock_knobs_given()
const {
398 return !interlock_method.empty() || interlock_maxpaths >= 0.0 ||
399 !interlock_refpath_scope.empty();
405 std::map<std::string, std::string> lld;
406 double nlp_alpha = -1.0;
408 long nlp_maxiter = -1;
409 std::string nlp_fork;
410 std::string warmstart;
411 long mol_iter_max = -1;
412 double mol_iter_tol = -1.0;
413 double mol_relax_factor = -1.0;
414 bool nlp_knobs_given()
const {
415 return !lld.empty() || nlp_alpha >= 0.0 || nlp_warm >= 0 || nlp_maxiter >= 0 || !nlp_fork.empty() ||
416 !warmstart.empty() || mol_iter_max >= 0 || mol_iter_tol >= 0.0 || mol_relax_factor >= 0.0;
419 std::string layer_solver;
430 std::string stage_solver;
436 std::string env_init;
446 std::string map_env_method;
447 std::size_t map_env_maxstages = 0;
450 std::string ln_transient;
451 std::string ln_transient_channels;
456 std::string sens_method;
457 std::string sens_scheme;
458 double sens_step = -1.0;
462 std::string uq_solver;
468 std::size_t tran_points = 0;
475 bool verbose =
false;
477 std::string remote_url;
478 int timeout_seconds = 0;
487 std::string ldes_tranfilter;
488 double ldes_warmupfrac = -1.0;
489 std::string ldes_cimethod;
490 bool ldes_cnvgon =
false;
491 double ldes_cnvgtol = -1.0;
492 bool ldes_slotted =
false;
493 double ldes_slotlength = -1.0;
500 bool slotted =
false;
501 double slotlength = -1.0;
502 int ldes_replications = 0;
503 int ldes_numthreads = 0;
504 double ldes_maxtime = -1.0;
505 std::vector<double> ldes_initsol;
506 std::string ldes_rest_url;
511 int jmt_replications = 0;
524 std::vector<long> state;
533 std::size_t events = 0;
541 double timestep = -1.0;
553 std::string transient_method;
560 double fau_epsilon = -1.0;
561 double fau_delta = -1.0;
568 std::string rate_sched;
570 std::size_t ctmc_tv_ngrid = 0;
577 std::vector<double> percentiles;
587 std::string verbosity;
595 std::string reward_name;
608 if (!k.map_env.empty()) c.
mode = k.map_env;
609 if (!k.map_env_method.empty()) c.
method = k.map_env_method;
610 if (k.map_env_maxstages) c.
max_stages = k.map_env_maxstages;
623std::vector<std::vector<std::size_t>> parse_cutoff_matrix(
const std::string& s) {
624 std::vector<std::vector<std::size_t>> out;
625 std::string::size_type pos = 0;
626 while (pos <= s.size()) {
627 const std::string::size_type semi = s.find(
';', pos);
628 const std::string row = s.substr(pos, semi == std::string::npos ? std::string::npos
630 std::vector<std::size_t> cells;
631 std::string::size_type cp = 0;
632 while (cp <= row.size()) {
633 const std::string::size_type comma = row.find(
',', cp);
634 const std::string cell = row.substr(cp, comma == std::string::npos ? std::string::npos
636 if (cell.empty())
return std::vector<std::vector<std::size_t>>();
637 for (std::string::size_type i = 0; i < cell.size(); ++i)
638 if (!std::isdigit(
static_cast<unsigned char>(cell[i])))
639 return std::vector<std::vector<std::size_t>>();
640 cells.push_back(
static_cast<std::size_t
>(std::atol(cell.c_str())));
641 if (comma == std::string::npos)
break;
644 if (cells.empty())
return std::vector<std::vector<std::size_t>>();
645 if (!out.empty() && cells.size() != out[0].size())
646 return std::vector<std::vector<std::size_t>>();
647 out.push_back(cells);
648 if (semi == std::string::npos)
break;
663std::string g_stdin_buf;
664bool g_stdin_loaded =
false;
673void set_stdin_override(
const std::string& text) {
675 g_stdin_loaded =
true;
678const std::string& stdin_model_text() {
679 if (!g_stdin_loaded) {
680 g_stdin_buf.assign(std::istreambuf_iterator<char>(std::cin),
681 std::istreambuf_iterator<char>());
682 g_stdin_loaded =
true;
695bool g_jsim_input =
false;
705bool g_pnml_input =
false;
714std::string g_verbosity =
"standard";
730void reset_invocation_state(
bool keep_stdin_override) {
731 g_json_output =
false;
732 g_jsim_input =
false;
733 g_pnml_input =
false;
734 g_verbosity =
"standard";
735 if (!keep_stdin_override) {
737 g_stdin_loaded =
false;
758 if (g_verbosity ==
"silent")
return;
762 if (!net.
raw_struct().priorities_ignored())
return;
764 "Warning: Priority classes are specified but no priority-aware scheduling "
765 "policy (PSPRIO, DPSPRIO, GPSPRIO, HOL, FCFSPRIO, FCFSPRPRIO, FCFSPIPRIO, "
766 "LCFSPRIO, LCFSPRPRIO, LCFSPIPRIO, SRPTPRIO) is used in the model. "
767 "Priorities will be ignored.\n");
778 warn_priorities_ignored(net);
783 std::remove(tmp.c_str());
784 warn_priorities_ignored(net);
794 warn_priorities_ignored(net);
799 std::remove(tmp.c_str());
800 warn_priorities_ignored(net);
805 warn_priorities_ignored(net);
808 std::istringstream
in(stdin_model_text());
809 line::io::detail::json root;
812 warn_priorities_ignored(net);
820 std::istringstream
in(stdin_model_text());
821 line::io::detail::json root;
828 double q, u, r, w, a, t;
854void emit_avg_table_named(
const std::vector<std::string>& stations,
855 const std::vector<std::string>& classes,
const char* arith,
859 for (
const char* key : {
"Station",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
861 rows[key] = line::reg::Json::array();
862 rows[
"type"] =
"AvgTable";
865 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen",
866 "Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
867 for (std::size_t i = 0; i < stations.size(); ++i) {
868 for (std::size_t c = 0; c < classes.size(); ++c) {
869 const AvgRow v =
get(i, c);
870 if (v.q == 0.0 && v.u == 0.0 && v.r == 0.0 && v.w == 0.0 && v.a == 0.0 && v.t == 0.0)
872 if (!g_json_output) {
873 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
874 stations[i].c_str(), classes[c].c_str(), v.q, v.u, v.r, v.w, v.a, v.t);
881 rows[
"Station"].push_back(stations[i]);
882 rows[
"JobClass"].push_back(classes[c]);
883 rows[
"QLen"].push_back(v.q);
884 rows[
"Util"].push_back(v.u);
885 rows[
"RespT"].push_back(v.r);
886 rows[
"ResidT"].push_back(v.w);
887 rows[
"ArvR"].push_back(v.a);
888 rows[
"Tput"].push_back(v.t);
895 for (line::reg::Json::const_iterator it = extra.begin(); it != extra.end(); ++it)
896 rows[it.key()] = it.value();
899 out[
"arith"] = arith;
900 out[
"method"] = method;
905 for (line::reg::Json::const_iterator it = envelope.begin(); it != envelope.end(); ++it)
906 out[it.key()] = it.value();
907 emit_document(dump_document(out));
920template <
class T,
class Get>
924 std::vector<std::string> stations, classes;
927 for (std::size_t i = 0; i < sn.
nstations; ++i) stations.push_back(sn.
stations[i].name);
928 for (std::size_t c = 0; c < sn.
nclasses; ++c) classes.push_back(sn.
classes[c].name);
941double cache_at(
const std::vector<T>& v, std::size_t r) {
942 if (r >= v.size())
return std::numeric_limits<double>::quiet_NaN();
962 if (cache.
empty())
return;
963 const double dnan = std::numeric_limits<double>::quiet_NaN();
964 std::printf(
"\n%-12s %-12s %5s %12s %12s %12s\n",
"Cache",
"JobClass",
"List",
"HitProb",
965 "DelayedHitP",
"MissProb");
966 for (std::size_t c = 0; c < cache.
caches.size(); ++c) {
968 const typename std::map<std::size_t, line::qn::CacheParam<T> >::const_iterator it =
971 const std::vector<std::size_t>& hitclass = it->second.hitclass;
972 for (std::size_t cl = 0; cl < sn.
nclasses; ++cl) {
976 if (cl >= hitclass.size() || hitclass[cl] == 0)
continue;
977 const double ph = cache_at(m.
hitprob, cl), pm = cache_at(m.
missprob, cl),
979 std::printf(
"%-12s %-12s %5d %12.6g %12.6g %12.6g\n", m.
name.c_str(),
980 sn.
classes[cl].name.c_str(), 0, ph, pd, pm);
982 std::printf(
"%-12s %-12s %5d %12.6g %12.6g %12.6g\n", m.
name.c_str(),
983 sn.
classes[cl].name.c_str(),
static_cast<int>(l + 1),
1011void emit_analysis(
const char* key,
const line::reg::Json& payload,
const std::string& method,
1020 for (line::reg::Json::const_iterator it = extra.begin(); it != extra.end(); ++it)
1021 body[it.key()] = it.value();
1025 if (!method.empty()) out[
"method"] = method;
1026 emit_document(dump_document(out));
1033 for (std::size_t i = 0; i < M.
rows(); ++i) {
1035 for (std::size_t j = 0; j < M.
cols(); ++j)
1037 rows.push_back(row);
1053 for (std::size_t i = 0; i < v.size(); ++i) a.push_back(v[i]);
1078 for (std::size_t c = 0; c < cache.
caches.size(); ++c) {
1103 caches.push_back(e);
1113inline line::reg::Json fj_extra_json(
const std::vector<std::string>& forks,
const std::vector<std::string>& joins,
1117 for (
const char* key : {
"Fork",
"Join",
"JobClass",
"FJQLen",
"FJRespT",
"FJTput"})
1118 t[key] = line::reg::Json::array();
1119 t[
"type"] =
"AvgFJTable";
1121 for (std::size_t p = 0; p < forks.size() && p < joins.size(); ++p)
1122 for (std::size_t c = 0; c < classes.size(); ++c) {
1123 const double q = (p < Q.
rows() && c < Q.
cols()) ? Q(p, c) : 0.0;
1124 const double r = (p < R.
rows() && c < R.
cols()) ? R(p, c) : 0.0;
1125 const double x = (p < X.
rows() && c < X.
cols()) ? X(p, c) : 0.0;
1126 if (!(q > 0.0 || r > 0.0 || x > 0.0))
continue;
1127 t[
"Fork"].push_back(forks[p]);
1128 t[
"Join"].push_back(joins[p]);
1129 t[
"JobClass"].push_back(classes[c]);
1130 t[
"FJQLen"].push_back(q);
1131 t[
"FJRespT"].push_back(r);
1132 t[
"FJTput"].push_back(x);
1139template <
class T,
class U>
1143 std::vector<std::string> forks, joins, classes;
1144 for (std::size_t p = 0; p < d.
npairs(); ++p) {
1145 forks.push_back(sn.
nodes[d.
forks[p] - 1].name);
1146 joins.push_back(sn.
nodes[d.
joins[p] - 1].name);
1148 for (std::size_t c = 0; c < sn.
nclasses; ++c) classes.push_back(sn.
classes[c].name);
1149 return fj_extra_json(forks, joins, classes, d.
QLen, d.
RespT, d.
Tput);
1180 std::fprintf(stderr,
"Warning: %s\n", r.
warning.c_str());
1188 for (std::size_t j = 0; j < r.
listcost.size(); ++j)
1190 extra[
"ListCost"] = lc;
1198 if (extra_in.is_object())
1199 for (line::reg::Json::const_iterator it = extra_in.begin(); it != extra_in.end(); ++it)
1200 extra[it.key()] = it.value();
1204 if (!r.
cache.empty()) extra[
"Cache"] = cache_extra_json<T>(r.
cache);
1208 if (!fjb.is_null()) extra[
"FJ"] = fjb;
1216 envelope[
"iter"] = r.
iter;
1223 emit_avg_table<T>(sn, r.
actualmethod, [&](std::size_t i, std::size_t c) {
1225 row.q = line::num_traits<T>::to_double(r.QN(i, c));
1226 row.u = line::num_traits<T>::to_double(r.UN(i, c));
1227 row.r = line::num_traits<T>::to_double(r.RN(i, c));
1228 row.w = line::num_traits<T>::to_double(r.WN(i, c));
1229 row.a = line::num_traits<T>::to_double(r.AN(i, c));
1230 row.t = line::num_traits<T>::to_double(r.TN(i, c));
1232 }, extra, envelope);
1245int solve_model_mva(
const std::string& file,
const Knobs& k) {
1248 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1249 if (k.tol >= 0.0) opt.
tol = k.tol;
1250 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
1251 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1252 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1253 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
1260 if constexpr (std::is_same_v<T, double>) {
1278 print_avg_table<T>(sn, r);
1303 if (g_json_output) {
1305 p[
"type"] =
"TranAvgTable";
1307 p[
"t0"] = k.t0 >= 0.0 ? k.t0 : 0.0;
1312 p[
"replications"] = r.
valid;
1313 p[
"replicationsRequested"] = reps;
1315 for (std::size_t i = 0; i < M && i < r.
QNt.size(); ++i)
1316 for (std::size_t c = 0; c < K && c < r.
QNt[i].size(); ++c) {
1323 e[
"Station"] = sn.
stations[i].name;
1324 e[
"JobClass"] = sn.
classes[c].name;
1327 line::reg::Json tt = line::reg::Json::array(), q = line::reg::Json::array(),
1328 u = line::reg::Json::array(), x = line::reg::Json::array();
1329 for (std::size_t j = 0; j < nt; ++j) {
1330 tt.push_back(r.
t[j]);
1331 q.push_back(r.
QNt[i][c][j]);
1332 u.push_back(r.
UNt[i][c][j]);
1333 x.push_back(r.
TNt[i][c][j]);
1339 curves.push_back(e);
1341 p[
"curves"] = curves;
1345 emit_analysis<double>(
"tran", p, std::string(
"jsim"));
1348 std::printf(
"SolverJMT arith=double method=jsim\n");
1349 std::printf(
"TranAvg times=%zu replications=%zu/%zu\n", nt, r.
valid, reps);
1350 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
1352 for (std::size_t i = 0; i < M && i < r.
QNt.size(); ++i)
1353 for (std::size_t c = 0; c < K && c < r.
QNt[i].size(); ++c) {
1355 for (std::size_t j = 0; j < nt; ++j)
1356 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
1358 r.
QNt[i][c][j], r.
UNt[i][c][j], r.
TNt[i][c][j]);
1378 std::size_t station = 1;
1384 if (k.node > sn.
nodes.size() || sn.
nodes[k.node - 1].station == 0)
1386 "--node " + std::to_string(k.node) +
1387 " is not a station, so it holds no per-class job count to take a law over");
1388 station = sn.
nodes[k.node - 1].station;
1391 std::vector<std::vector<double> > states;
1392 const std::pair<std::vector<double>, std::vector<std::vector<double> > > r =
1394 const std::vector<double>& t = r.first;
1395 const std::vector<std::vector<double> >& pit = r.second;
1397 if (g_json_output) {
1399 p[
"type"] =
"TranProb";
1401 p[
"scope"] = sn.
stations[station - 1].name;
1403 p[
"station"] = station - 1;
1404 p[
"replications"] = reps;
1409 span.push_back(k.t0 >= 0.0 ? k.t0 : 0.0);
1412 p[
"t"] = vector_json(t);
1416 for (std::size_t s = 0; s < states.size(); ++s) labels.push_back(vector_json(states[s]));
1417 p[
"labelsAggr"] = labels;
1419 for (std::size_t g = 0; g < pit.size(); ++g) rows.push_back(vector_json(pit[g]));
1420 p[
"pitAggr"] = rows;
1421 emit_analysis<double>(
"tranprob", p, std::string(
"jsim"));
1424 std::printf(
"SolverJMT arith=double method=jsim\n");
1425 std::printf(
"TranProb times=%zu tspan=%g:%g scope=%s replications=%zu\n", t.size(),
1427 sn.
stations[station - 1].name.c_str(), reps);
1431 std::printf(
"%8s %s\n",
"State",
"Aggregate");
1432 for (std::size_t s = 0; s < states.size(); ++s) {
1433 std::printf(
"%8zu ", s + 1);
1434 for (std::size_t c = 0; c < states[s].size(); ++c) std::printf(
" %g", states[s][c]);
1437 std::printf(
"%14s",
"Time");
1438 for (std::size_t s = 0; s < states.size(); ++s) std::printf(
" %12zu", s + 1);
1440 for (std::size_t g = 0; g < t.size(); ++g) {
1441 std::printf(
"%14.8g", t[g]);
1442 for (std::size_t s = 0; s < states.size(); ++s)
1443 std::printf(
" %12.6g", s < pit[g].size() ? pit[g][s] : 0.0);
1461 const std::size_t nevents = k.events ? k.events : 0;
1465 if (k.node > sn.
nodes.size() || sn.
nodes[k.node - 1].station == 0)
1467 "--node " + std::to_string(k.node) +
1468 " is not a station, so no per-class job count is logged for it");
1471 if (g_json_output) {
1473 p[
"type"] =
"SamplePath";
1475 p[
"scope"] = sn.
nodes[k.node - 1].name;
1476 p[
"node"] = k.node - 1;
1477 p[
"events"] = nevents;
1479 p[
"drawn"] = tr.t.size();
1480 p[
"t"] = vector_json(tr.t);
1482 for (std::size_t j = 0; j < tr.qlen.size(); ++j) st.push_back(vector_json(tr.qlen[j]));
1485 for (std::size_t c = 0; c < K; ++c) cols.push_back(sn.
classes[c].name);
1486 p[
"columns"] = cols;
1487 emit_analysis<double>(
"sample", p, std::string(
"jsim"));
1490 std::printf(
"SolverJMT arith=double method=jsim\n");
1491 std::printf(
"%14s %s\n",
"Time", (
"NodeState (" + sn.
nodes[k.node - 1].name +
1492 ", per class)").c_str());
1493 for (std::size_t j = 0; j < tr.t.size(); ++j) {
1494 std::printf(
"%14.8g ", tr.t[j]);
1495 for (std::size_t c = 0; c < K && c < tr.qlen[j].size(); ++c)
1496 std::printf(
" %g", tr.qlen[j][c]);
1503 const std::size_t M = sn.
nstations, n = tr.t.size();
1504 if (g_json_output) {
1506 p[
"type"] =
"SamplePath";
1508 p[
"scope"] =
"(system)";
1509 p[
"events"] = nevents;
1512 p[
"t"] = vector_json(tr.t);
1514 for (std::size_t j = 0; j < n; ++j) {
1516 for (std::size_t i = 0; i < M; ++i)
1517 for (std::size_t c = 0; c < K; ++c)
1518 row.push_back(i < tr.state.size() && j < tr.state[i].size() &&
1519 c < tr.state[i][j].size()
1526 for (std::size_t i = 0; i < M; ++i)
1527 for (std::size_t c = 0; c < K; ++c)
1529 p[
"columns"] = cols;
1530 emit_analysis<double>(
"sample", p, std::string(
"jsim"));
1533 std::printf(
"SolverJMT arith=double method=jsim\n");
1534 std::printf(
"%14s %s\n",
"Time",
"SysState (station-major, per class)");
1535 for (std::size_t j = 0; j < n; ++j) {
1536 std::printf(
"%14.8g ", tr.t[j]);
1537 for (std::size_t i = 0; i < M; ++i)
1538 for (std::size_t c = 0; c < K; ++c)
1539 std::printf(
" %g", i < tr.state.size() && j < tr.state[i].size() &&
1540 c < tr.state[i][j].size()
1557int solve_model_jmt(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
1562 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
1563 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
1564 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
1569 if (k.jmt_replications > 0) o.
replications = k.jmt_replications;
1571 if (k.jmt_replications > 0 && o.
method ==
"jsim")
1573 "--jmt-replications sizes the transient ensemble of --method default; --method jsim "
1574 "is a single JSIM run, so the count would be ignored rather than honoured");
1577 if (analysis ==
"tran")
return solve_model_jmt_tran(sn, o, k);
1578 if (analysis ==
"tranprob")
return solve_model_jmt_tranprob(sn, o, k);
1579 if (analysis ==
"sample")
return solve_model_jmt_sample(sn, o, k);
1587 if (analysis ==
"prob") {
1588 std::size_t target = 0;
1590 if (k.node <= sn.
nodes.size()) target = sn.
nodes[k.node - 1].station;
1593 "--node " + std::to_string(k.node) +
1594 " is not a station, so it holds no per-class job count to ask about");
1597 sn, o, target, std::vector<double>(k.state.begin(), k.state.end()));
1598 if (g_json_output) {
1600 p[
"type"] =
"ProbAggr";
1602 p[
"ProbSysAggr"] = r.
sys;
1608 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
1609 sv = line::reg::Json::array();
1610 for (std::size_t i = 0; i < sn.
nstations; ++i) {
1617 p[
"StateSeen"] = sv;
1622 emit_analysis<double>(
"prob", p, std::string(
"jsim"));
1625 std::printf(
"SolverJMT arith=double method=jsim\n");
1626 std::printf(
"ProbSysAggr %.10g%s\n", r.
sys, r.
sys_seen ?
"" :
" (state never observed)");
1627 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
1628 for (std::size_t i = 0; i < sn.
nstations; ++i)
1629 std::printf(
"%-16s %14.10g%s\n", sn.
stations[i].name.c_str(), r.
station[i],
1634 if (analysis ==
"cdf" || analysis ==
"trancdf" || analysis ==
"trancdfpasst") {
1638 const std::map<std::pair<std::size_t, std::size_t>,
1639 std::vector<std::pair<double, double> > >
1642 for (std::map<std::pair<std::size_t, std::size_t>,
1643 std::vector<std::pair<double, double> > >::const_iterator it = rd.begin();
1644 it != rd.end(); ++it) {
1646 for (std::size_t i = 0; i < it->second.size(); ++i) {
1648 row.push_back(it->second[i].first);
1649 row.push_back(it->second[i].second);
1650 rows.push_back(row);
1652 const std::string key =
1654 sn.
classes[it->first.second - 1].name;
1657 std::printf(
"%-24s %8zu points respT(max)=%.6g\n", key.c_str(),
1658 it->second.size(), it->second.back().second);
1660 if (g_json_output) {
1663 emit_document(dump_document(out, 2));
1669 std::printf(
"SolverJMT arith=double method=%s\n", r.
avg.actualmethod.c_str());
1681 for (
int m = 0; m < 6; ++m) {
1683 for (std::size_t i = 0; i < M; ++i)
1684 for (std::size_t c = 0; c < K; ++c) t(i, c) = (*src[m])(i, c);
1697 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array();
1698 line::reg::Json rt = line::reg::Json::array(), w = line::reg::Json::array();
1699 line::reg::Json a = line::reg::Json::array(), t = line::reg::Json::array();
1700 for (std::size_t f = 0; f < sn.
regions.size() && M + f < r.
avg.QN.rows(); ++f) {
1701 names.push_back(f < sn.
regions.size() && !sn.
regions[f].name.empty()
1703 :
"FCR" + std::to_string(f + 1));
1704 for (std::size_t c = 0; c < K; ++c) {
1705 q.push_back(r.
avg.QN(M + f, c));
1706 u.push_back(r.
avg.UN(M + f, c));
1707 rt.push_back(r.
avg.RN(M + f, c));
1708 w.push_back(r.
avg.WN(M + f, c));
1709 a.push_back(r.
avg.AN(M + f, c));
1710 t.push_back(r.
avg.TN(M + f, c));
1713 fcr_extra[
"Region"] = names;
1714 fcr_extra[
"QLen"] = q;
1715 fcr_extra[
"Util"] = u;
1716 fcr_extra[
"RespT"] = rt;
1717 fcr_extra[
"ResidT"] = w;
1718 fcr_extra[
"ArvR"] = a;
1719 fcr_extra[
"Tput"] = t;
1722 if (!fcr_extra.empty()) avg_extra[
"FCR"] = fcr_extra;
1730 if (!r.
QCI.empty()) ci[
"QNCI"] = matrix_json<double>(r.
QCI);
1731 if (!r.
UCI.empty()) ci[
"UNCI"] = matrix_json<double>(r.
UCI);
1732 if (!r.
RCI.empty()) ci[
"RNCI"] = matrix_json<double>(r.
RCI);
1733 if (!r.
TCI.empty()) ci[
"TNCI"] = matrix_json<double>(r.
TCI);
1734 if (!r.
ACI.empty()) ci[
"ANCI"] = matrix_json<double>(r.
ACI);
1735 if (!ci.empty()) avg_extra[
"CI"] = ci;
1736 print_avg_table<double>(sn, table, avg_extra);
1741 if (!g_json_output && !r.
QCI.empty()) {
1742 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLenCI",
1743 "UtilCI",
"RespTCI",
"TputCI",
"ArvRCI");
1744 for (std::size_t i = 0; i < sn.
nstations && i < r.
QCI.rows(); ++i)
1745 for (std::size_t c = 0; c < sn.
nclasses; ++c)
1746 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
1748 r.
UCI.empty() ? 0.0 : r.
UCI(i, c), r.
RCI.empty() ? 0.0 : r.
RCI(i, c),
1749 r.
TCI.empty() ? 0.0 : r.
TCI(i, c),
1750 r.
ACI.empty() ? 0.0 : r.
ACI(i, c));
1753 if (!g_json_output && r.
TNfcr.rows() > 0) {
1754 std::printf(
"%-16s %-14s %12s %12s\n",
"Region",
"JobClass",
"Tput",
"DropRate");
1755 for (std::size_t f = 0; f < r.
TNfcr.rows(); ++f)
1756 for (std::size_t c = 0; c < sn.
nclasses; ++c)
1761 std::printf(
"%-16s %-14s %12.6g %12.6g\n",
1764 :
"FCR" + std::to_string(f + 1))
1769 for (std::map<std::size_t, std::vector<double> >::const_iterator it =
1772 for (std::size_t c = 0; c < it->second.size(); ++c)
1773 std::printf(
"%-16s %-14s hitProb=%12.6g\n", sn.
nodes[it->first - 1].name.c_str(),
1774 sn.
classes[c].name.c_str(), it->second[c]);
1794 "the cftp methods draw random states and form the station balance functions in the "
1795 "log domain, neither of which exists in exact rational arithmetic; rerun with --arith "
1796 "double or --arith real");
1806 double horizon = 0.0;
1807 for (std::size_t i = 0; i < s.
horizon.size(); ++i)
1808 horizon +=
static_cast<double>(s.
horizon[i]);
1809 if (!s.
horizon.empty()) horizon /=
static_cast<double>(s.
horizon.size());
1811 "SolverNC arith=%s method=%s type=%s samples=%zu seed=%lu distinct=%zu "
1812 "meanhorizon=%.6g\n",
1816 print_avg_table<T>(sn, r);
1830int solve_model_nc(
const std::string& file,
const Knobs& k) {
1833 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1834 if (k.tol >= 0.0) opt.
tol = k.tol;
1835 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
1836 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1837 if (k.samples) opt.
samples = k.samples;
1838 if (k.seed) opt.
seed = k.seed;
1839 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1840 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
1841 if (k.slotted) opt.
slotted =
true;
1842 if (k.slotlength > 0.0) {
1849 if (opt.
method ==
"cftp" || opt.
method ==
"cftp.approx") {
1853 return solve_nc_cftp_avg<T>(net.
get_struct(), opt, cftpopt);
1857 if constexpr (std::is_same_v<T, double>) {
1873 std::printf(
"SolverNC arith=%s method=%s type=%s lognormconst=%.10g\n",
1877 print_avg_table<T>(sn, r);
1893int solve_model_mam(
const std::string& file,
const Knobs& k) {
1903 print_avg_table<T>(sn, r);
1915 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1916 if (k.tol >= 0.0) opt.
tol = k.tol;
1917 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1918 if (k.max_states > 0) opt.
max_states =
static_cast<std::size_t
>(k.max_states);
1919 if (!k.nonmkv.empty()) opt.
nonmkv = k.nonmkv;
1936int solve_model_ag(
const std::string& file,
const Knobs& k) {
1939 apply_ag_knobs(k, opt);
1946 print_avg_table<T>(sn, r);
1961 if (k.node)
return k.node;
1962 for (std::size_t a = 0; a < sn.
nof_nodes(); ++a)
1963 if (sn.
nodes[a].nodetype == line::qn::NodeType::Queue)
return a + 1;
1965 "the MAM per-node analyses report a queue's internals and this model has no Queue node");
1971 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1972 if (k.tol >= 0.0) opt.
tol = k.tol;
1973 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1974 if (k.cutoff >= 0.0) opt.
cutoff =
static_cast<std::size_t
>(k.cutoff);
1975 if (!k.nonmkv.empty()) opt.
nonmkv = k.nonmkv;
1976 if (k.fj_accuracy > 0) opt.
fj_accuracy =
static_cast<std::size_t
>(k.fj_accuracy);
1977 if (!k.fj_tmode.empty()) opt.
fj_tmode = k.fj_tmode;
1978 if (k.sync_phases >= 0) opt.
sync_phases =
static_cast<std::size_t
>(k.sync_phases);
1979 if (k.mapmsg_quantization >= 0)
1981 if (!k.timescale.empty()) opt.
timescale = k.timescale;
1982 if (k.slotlength > 0.0) opt.
slotlength = k.slotlength;
2004int solve_model_mam_prob(
const std::string& file,
const Knobs& k) {
2011 const std::size_t node = mam_query_node<T>(sn, k);
2012 const std::size_t ist = sn.
nodes[node - 1].station;
2014 std::vector<std::vector<T> > marg(sn.
nclasses);
2015 for (std::size_t r = 0; r < sn.
nclasses; ++r)
2018 if (g_json_output) {
2020 p[
"type"] =
"ProbTable";
2022 p[
"node"] = node - 1;
2023 p[
"Node"] = sn.
nodes[node - 1].name;
2024 p[
"levels"] =
P.P.rows();
2025 p[
"phases"] =
P.P.cols();
2027 for (std::size_t n = 0; n <
P.P.rows(); ++n) {
2029 for (std::size_t j = 0; j <
P.P.cols(); ++j)
2031 joint.push_back(row);
2035 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
2037 e[
"JobClass"] = sn.
classes[r].name;
2039 e[
"P"] = vector_json(marg[r]);
2046 std::printf(
"SolverMAM arith=%s method=%s node=%s levels=%zu phases=%zu\n",
2048 sn.
nodes[node - 1].name.c_str(),
P.P.rows(),
P.P.cols());
2049 std::printf(
"%-8s %-8s %16s\n",
"Level",
"Phase",
"Prob");
2050 for (std::size_t n = 0; n <
P.P.rows(); ++n)
2051 for (std::size_t j = 0; j <
P.P.cols(); ++j)
2052 std::printf(
"%-8zu %-8zu %16.10g\n", n, j + 1,
2054 std::printf(
"%-14s %-8s %16s\n",
"JobClass",
"Jobs",
"Prob");
2055 for (std::size_t r = 0; r < sn.
nclasses; ++r)
2056 for (std::size_t n = 0; n < marg[r].size(); ++n)
2057 std::printf(
"%-14s %-8zu %16.10g\n", sn.
classes[r].name.c_str(), n,
2066std::vector<double> percentile_levels(
const Knobs& k) {
2067 if (!k.percentiles.empty())
return k.percentiles;
2068 std::vector<double> pcts;
2069 pcts.push_back(0.50);
2070 pcts.push_back(0.90);
2071 pcts.push_back(0.95);
2072 pcts.push_back(0.99);
2097int solve_model_mam_cdf(
const std::string& file,
const Knobs& k,
const char* key,
2102 const std::vector<double> pcts = percentile_levels(k);
2105 const std::vector<std::vector<T> > perc =
2107 if (g_json_output) {
2109 p[
"type"] =
"PerctRespT";
2111 p[
"source"] =
"fjcodes";
2113 for (std::size_t r = 0; r < perc.size(); ++r) {
2115 e[
"JobClass"] = sn.
classes[r].name;
2117 e[
"percentileLevels"] = pcts;
2118 e[
"percentiles"] = vector_json(perc[r]);
2122 emit_analysis<T>(key, p, opt.
method);
2127 std::printf(
"# the response-time CDF has no fork-join route in the reference; these are "
2128 "the FJ_codes percentiles getPerctRespT reads\n");
2129 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
2130 for (std::size_t r = 0; r < perc.size(); ++r)
2131 for (std::size_t j = 0; j < perc[r].size(); ++j)
2132 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
2138 std::vector<std::vector<T> > perc;
2139 for (std::size_t r = 0; r < rd.size(); ++r)
2142 if (g_json_output) {
2147 for (std::size_t r = 0; r < rd.size(); ++r) {
2151 if (rd[r].X.
empty())
continue;
2153 e[
"JobClass"] = sn.
classes[r].name;
2155 e[
"t"] = vector_json(rd[r].X);
2156 e[
"F"] = vector_json(rd[r].F);
2157 e[
"percentileLevels"] = pcts;
2158 e[
"percentiles"] = vector_json(perc[r]);
2162 emit_analysis<T>(key, p, opt.
method);
2167 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Time",
"F(t)");
2168 for (std::size_t r = 0; r < rd.size(); ++r)
2169 for (std::size_t j = 0; j < rd[r].X.size(); ++j)
2170 std::printf(
"%-14s %14.8g %14.10g\n", sn.
classes[r].name.c_str(),
2173 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
2174 for (std::size_t r = 0; r < perc.size(); ++r)
2175 for (std::size_t j = 0; j < perc[r].size(); ++j)
2176 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
2193int solve_model_mam_perct(
const std::string& file,
const Knobs& k) {
2197 const std::vector<double> pcts = percentile_levels(k);
2199 std::vector<std::vector<T> > perc;
2204 const std::vector<line::mam::RespTCdf<T> > rd =
2206 for (std::size_t r = 0; r < rd.size(); ++r)
2210 if (g_json_output) {
2212 p[
"type"] =
"PerctRespT";
2214 p[
"source"] = fj ?
"fjcodes" :
"cdf";
2216 for (std::size_t r = 0; r < perc.size(); ++r) {
2217 if (perc[r].empty())
continue;
2219 e[
"JobClass"] = sn.
classes[r].name;
2221 e[
"percentileLevels"] = pcts;
2222 e[
"percentiles"] = vector_json(perc[r]);
2226 emit_analysis<T>(
"perct", p, opt.
method);
2229 std::printf(
"SolverMAM arith=%s method=%s classes=%zu source=%s\n",
2231 fj ?
"fjcodes" :
"cdf");
2232 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
2233 for (std::size_t r = 0; r < perc.size(); ++r)
2234 for (std::size_t j = 0; j < perc[r].size(); ++j)
2235 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
2250int solve_model_mam_tran(
const std::string& file,
const Knobs& k) {
2256 if (g_json_output) {
2258 p[
"type"] =
"TranAvgTable";
2263 for (std::size_t i = 0; i < tr.Qt.size(); ++i)
2264 for (std::size_t r = 0; r < tr.Qt[i].size(); ++r) {
2265 if (tr.Qt[i][r].times.empty())
continue;
2267 e[
"Station"] = sn.
stations[i].name;
2268 e[
"JobClass"] = sn.
classes[r].name;
2271 e[
"t"] = tr.Qt[i][r].times;
2272 e[
"QLen"] = vector_json(tr.Qt[i][r].values);
2273 e[
"Util"] = vector_json(tr.Ut[i][r].values);
2274 e[
"Tput"] = vector_json(tr.Tt[i][r].values);
2278 emit_analysis<T>(
"tran", p,
"ldqbd");
2283 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
2285 for (std::size_t i = 0; i < tr.Qt.size(); ++i)
2286 for (std::size_t r = 0; r < tr.Qt[i].size(); ++r)
2287 for (std::size_t j = 0; j < tr.Qt[i][r].times.size(); ++j)
2288 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
2290 tr.Qt[i][r].times[j],
2308int solve_model_mam_internals(
const std::string& file,
const Knobs& k) {
2315 if (g_json_output) {
2317 p[
"type"] =
"MAMResult";
2321 p[
"uniformization"] = q;
2331 p[
"theta"] = vector_json(r.
theta);
2332 p[
"alpha"] = vector_json(r.
alpha);
2334 for (std::size_t n = 0; n < r.
levelProb.rows(); ++n) {
2336 for (std::size_t j = 0; j < r.
levelProb.cols(); ++j)
2340 p[
"levelProb"] = lv;
2341 emit_analysis<T>(
"internals", p, std::string());
2345 std::printf(
"lambda=%.10g rho=%.10g q=%.10g drift=%.10g decayRate=%.10g\n",
2348 std::printf(
"pi0=%.10g QLen=%.10g Util=%.10g Tput=%.10g truncLevel=%zu truncError=%.3g "
2355 std::printf(
"%-8s %16s\n",
"Level",
"Prob");
2356 for (std::size_t n = 0; n < r.
levelProb.rows(); ++n) {
2358 for (std::size_t j = 0; j < r.
levelProb.cols(); ++j)
2360 std::printf(
"%-8zu %16.10g\n", n, s);
2366line::reg::Json read_json_arg(
const std::string& spec,
const char* flag) {
2367 std::string text = spec;
2368 const std::size_t at = spec.find_first_not_of(
" \t\r\n");
2369 if (at == std::string::npos || (spec[at] !=
'{' && spec[at] !=
'[')) {
2370 std::ifstream
in(spec.c_str());
2372 throw line::InputError(std::string(flag) +
" is neither inline JSON nor a readable "
2373 "file (got '" + spec +
"')");
2374 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
2377 return line::reg::Json::parse(text);
2378 }
catch (
const line::reg::Json::parse_error& e) {
2379 throw line::InputError(std::string(
"malformed ") + flag +
" JSON: " + e.what());
2384std::vector<std::vector<int> > json_int_table(
const line::reg::Json& j,
const char* name) {
2386 throw line::InputError(std::string(
"--qrf-params ") + name +
" must be an array of rows");
2387 std::vector<std::vector<int> > out;
2388 for (std::size_t m = 0; m < j.size(); ++m) {
2389 if (!j[m].is_array())
2390 throw line::InputError(std::string(
"--qrf-params ") + name +
" must be an array of "
2392 std::vector<int> row;
2393 for (std::size_t c = 0; c < j[m].size(); ++c) row.push_back(j[m][c].get<
int>());
2409 if (!j.is_object())
throw line::InputError(
"--qrf-params must be a JSON object");
2410 const char* required[] = {
"f",
"MR",
"BB",
"MM",
"MM1",
"ZZ"};
2411 std::string missing;
2412 for (std::size_t i = 0; i < 6; ++i)
2413 if (!j.contains(required[i]))
2414 missing += (missing.empty() ?
"" :
", ") + std::string(required[i]);
2415 if (!missing.empty())
2417 "; required are f, MR, BB, MM, MM1, ZZ (F is optional, ZM is "
2418 "derived from ZZ)");
2421 p.
f = j[
"f"].get<
int>();
2422 p.
MR = j[
"MR"].get<
int>();
2423 p.
BB = json_int_table(j[
"BB"],
"BB");
2424 p.
MM = json_int_table(j[
"MM"],
"MM");
2425 p.
MM1 = json_int_table(j[
"MM1"],
"MM1");
2426 if (!j[
"ZZ"].is_array())
throw line::InputError(
"--qrf-params ZZ must be an array");
2427 for (std::size_t i = 0; i < j[
"ZZ"].size(); ++i) p.
ZZ.push_back(j[
"ZZ"][i].get<
int>());
2428 if (j.contains(
"F"))
2429 for (std::size_t i = 0; i < j[
"F"].size(); ++i) p.
F.push_back(j[
"F"][i].get<
int>());
2436 if (!j.is_array() || j.empty() || !j[0].is_array())
2437 throw line::InputError(
"--qrf-alpha must be a JSON array of rows, one per station");
2439 for (std::size_t i = 0; i < j.size(); ++i) {
2440 if (!j[i].is_array() || j[i].size() != j[0].size())
2442 for (std::size_t n = 0; n < j[i].size(); ++n) a(i, n) = j[i][n].get<
double>();
2449 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
2450 if (k.level > 0) opt.
level = k.level;
2451 if (!k.qrf_params.empty()) decode_qrf_params(k.qrf_params, opt);
2452 if (!k.qrf_alpha.empty()) decode_qrf_alpha(k.qrf_alpha, opt);
2465int solve_model_ba(
const std::string& file,
const Knobs& k) {
2468 apply_ba_knobs(k, opt);
2475 print_avg_table<T>(sn, r);
2494int solve_model_ba_bounds(
const std::string& file,
const Knobs& k) {
2497 apply_ba_knobs(k, opt);
2503 if (g_json_output) {
2505 p[
"type"] =
"BoundsTable";
2507 p[
"family"] = am.substr(0, am.find(
'.'));
2510 for (
const char* key : {
"Station",
"JobClass",
"QLower",
"QUpper",
"TLower",
"TUpper"})
2511 p[key] = line::reg::Json::array();
2512 for (std::size_t i = 0; i < sn.
nstations; ++i)
2513 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2514 if (!b.
keep[i][c])
continue;
2515 p[
"Station"].push_back(sn.
stations[i].name);
2516 p[
"JobClass"].push_back(sn.
classes[c].name);
2517 p[
"QLower"].push_back(d(b.
Qlower(i, c)));
2518 p[
"QUpper"].push_back(d(b.
Qupper(i, c)));
2519 p[
"TLower"].push_back(d(b.
Tlower(i, c)));
2520 p[
"TUpper"].push_back(d(b.
Tupper(i, c)));
2522 emit_analysis<T>(
"bounds", p, am);
2525 std::printf(
"SolverBA arith=%s method=%s type=%s family=%s sides=%s\n",
2529 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLower",
"QUpper",
2530 "TLower",
"TUpper");
2531 for (std::size_t i = 0; i < sn.
nstations; ++i)
2532 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2533 if (!b.
keep[i][c])
continue;
2534 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
2550int solve_model_lqns_network(
const std::string& file,
const Knobs& k) {
2553 if (!k.method.empty()) opt.
method = k.method;
2554 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
2559 opt.
timeout = k.timeout_seconds;
2568 print_avg_table<T>(sn, r);
2585 const std::vector<std::vector<line::solvers::DefaultCdfCurve>> RD =
2587 if (g_json_output) {
2589 p[
"type"] =
"CdfRespT";
2592 for (std::size_t i = 0; i < RD.size(); ++i)
2593 for (std::size_t c = 0; c < RD[i].size(); ++c) {
2594 if (RD[i][c].t.empty())
continue;
2596 e[
"Station"] = sn.
stations[i].name;
2597 e[
"JobClass"] = sn.
classes[c].name;
2605 emit_analysis<T>(
"cdf", p, std::string());
2608 std::printf(
"%s arith=%s method=%s (exponential fallback with the solver's mean)\n",
2610 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
2611 for (std::size_t i = 0; i < RD.size(); ++i)
2612 for (std::size_t c = 0; c < RD[i].size(); ++c)
2613 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
2614 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
2615 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].F[j]);
2621int solve_model_mva_cdf(
const std::string& file,
const Knobs& k) {
2624 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
2625 if (k.tol >= 0.0) opt.
tol = k.tol;
2626 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
2627 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
2628 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
2629 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
2632 return emit_default_cdf<T>(
"SolverMVA", net.
get_struct(), r);
2637int solve_model_ag_cdf(
const std::string& file,
const Knobs& k) {
2640 apply_ag_knobs(k, opt);
2642 return emit_default_cdf<T>(
"SolverAG", net.
get_struct(), r);
2647int solve_model_ba_cdf(
const std::string& file,
const Knobs& k) {
2650 apply_ba_knobs(k, opt);
2652 return emit_default_cdf<T>(
"SolverBA", net.
get_struct(), r);
2656int solve_model_lqns_network_cdf(
const std::string& file,
const Knobs& k) {
2659 if (!k.method.empty()) opt.
method = k.method;
2660 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
2661 opt.
timeout = k.timeout_seconds;
2665 return emit_default_cdf<double>(
"SolverLQNS", net.
get_struct(), r);
2678 const std::string& stage, std::size_t npriors) {
2683 p[
"type"] =
"IntervalTable";
2685 p[
"exact"] = iv.
exact;
2686 p[
"intervalMethod"] = iv.
method;
2687 if (!iv.
why.empty()) p[
"why"] = iv.
why;
2694 for (
const char* key : {
"Station",
"JobClass",
"QLen_lo",
"QLen_up",
"Util_lo",
"Util_up",
2695 "RespT_lo",
"RespT_up",
"Tput_lo",
"Tput_up"})
2696 p[key] = line::reg::Json::array();
2697 if (!g_json_output) {
2698 std::printf(
"SolverUQ arith=%s interval=%s exact=%s priors=%zu stage=%s\n",
2700 npriors, stage.c_str());
2706 std::fprintf(stderr,
2707 "Warning: exact interval MVA does not apply (%s); the range below is "
2708 "over the solved design points and is not an enclosure.\n",
2710 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s %12s %12s\n",
"Station",
2711 "JobClass",
"QLen_lo",
"QLen_up",
"Util_lo",
"Util_up",
"RespT_lo",
2712 "RespT_up",
"Tput_lo",
"Tput_up");
2714 for (std::size_t i = 0; i < sn.
nstations; ++i)
2715 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2718 if (v(iv.
Qup, i, c) <= 0.0 && v(iv.
Uup, i, c) <= 0.0 && v(iv.
Tup, i, c) <= 0.0)
2720 if (!g_json_output) {
2722 "%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
2724 v(iv.
Qup, i, c), v(iv.
Ulo, i, c), v(iv.
Uup, i, c), v(iv.
Rlo, i, c),
2725 v(iv.
Rup, i, c), v(iv.
Tlo, i, c), v(iv.
Tup, i, c));
2728 p[
"Station"].push_back(sn.
stations[i].name);
2729 p[
"JobClass"].push_back(sn.
classes[c].name);
2730 p[
"QLen_lo"].push_back(v(iv.
Qlo, i, c));
2731 p[
"QLen_up"].push_back(v(iv.
Qup, i, c));
2732 p[
"Util_lo"].push_back(v(iv.
Ulo, i, c));
2733 p[
"Util_up"].push_back(v(iv.
Uup, i, c));
2734 p[
"RespT_lo"].push_back(v(iv.
Rlo, i, c));
2735 p[
"RespT_up"].push_back(v(iv.
Rup, i, c));
2736 p[
"Tput_lo"].push_back(v(iv.
Tlo, i, c));
2737 p[
"Tput_up"].push_back(v(iv.
Tup, i, c));
2739 if (g_json_output) emit_analysis<T>(
"interval", p, iv.
method);
2763int solve_model_uq(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
2766 if (!k.method.empty()) opt.
method = k.method;
2767 if (k.samples) opt.
samples = k.samples;
2768 if (k.seed) opt.
seed = k.seed;
2776 if (analysis ==
"interval") {
2791 if (analysis ==
"avg") {
2793 std::printf(
"SolverUQ arith=%s design=%s points=%zu priors=%zu stage=%s method=%s\n",
2796 print_avg_table<T>(sn, r.
avg);
2802 p[
"type"] =
"PosteriorTable";
2807 for (std::size_t l = 0; l < r.
sites.size(); ++l) {
2809 s[
"node"] = sn.
nodes[r.
sites[l].node - 1].name;
2811 s[
"kind"] = r.
sites[l].arrival ?
"arrival" :
"service";
2814 p[
"priors"] = sites;
2815 for (
const char* key : {
"Point",
"Weight",
"Station",
"JobClass",
"QLen",
"Util",
"RespT",
2817 p[key] = line::reg::Json::array();
2820 if (!g_json_output) {
2821 std::printf(
"SolverUQ arith=%s design=%s points=%zu priors=%zu stage=%s\n",
2826 std::printf(
"%-6s %12s substituted means\n",
"Point",
"Weight");
2827 for (std::size_t e = 0; e < r.
points.size(); ++e) {
2829 for (std::size_t l = 0; l < r.
sites.size(); ++l)
2830 std::printf(
" %s@%s=%.6g", sn.
nodes[r.
sites[l].node - 1].name.c_str(),
2835 std::printf(
"%-6s %12s %-16s %-14s %12s %12s %12s %12s\n",
"Point",
"Weight",
"Station",
2836 "JobClass",
"QLen",
"Util",
"RespT",
"Tput");
2838 for (std::size_t e = 0; e < r.
points.size(); ++e) {
2840 for (std::size_t l = 0; l < r.
sites.size(); ++l)
2842 means.push_back(row);
2844 for (std::size_t i = 0; i < sn.
nstations; ++i)
2845 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2853 if (q <= 0.0 && u <= 0.0 && t <= 0.0)
continue;
2854 if (!g_json_output) {
2855 std::printf(
"%-6zu %12.6g %-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", e + 1,
2861 p[
"Point"].push_back(e);
2863 p[
"Station"].push_back(sn.
stations[i].name);
2864 p[
"JobClass"].push_back(sn.
classes[c].name);
2865 p[
"QLen"].push_back(q);
2866 p[
"Util"].push_back(u);
2867 p[
"RespT"].push_back(rr);
2868 p[
"Tput"].push_back(t);
2871 if (g_json_output) {
2872 p[
"substitutedMean"] = means;
2873 emit_analysis<T>(
"posterior", p, r.
avg.actualmethod);
2887 std::size_t cut = 0;
2888 for (std::size_t i = 0; i < d.
cutoff.size(); ++i) cut = std::max(cut, d.
cutoff[i]);
2890 std::printf(
"SolverCTMC arith=%s method=%s type=%s states=%zu cutoff=%zu\n",
2895 std::printf(
"SolverCTMC arith=%s method=%s type=%s states=%zu\n",
2901 if (!d.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", d.
warning.c_str());
2916 m[
"states"] = d.
chain.space.size();
2917 std::size_t cut = 0;
2918 for (std::size_t i = 0; i < d.
cutoff.size(); ++i) cut = std::max(cut, d.
cutoff[i]);
2919 if (cut) m[
"cutoff"] = cut;
2934 print_ctmc_banner<T>(a.
sol);
2935 print_avg_table<T>(sn, r);
2940 std::printf(
"%-16s %-14s %12s\n",
"Region",
"JobClass",
"Parked");
2941 for (std::size_t c = 0; c < sn.
nclasses && c < a.
parked.size(); ++c)
2942 std::printf(
"%-16s %-14s %12.6g\n",
"(all)", sn.
classes[c].name.c_str(),
2969 std::size_t held = 0;
2972 "SolverCTMC arith=%s method=%s type=%s states=%lld held=%zu levels=%zu iters=%d "
2973 "encoding=%s exact=%s\n",
2981 print_avg_table<T>(sn, r);
3008 if (!line::ctmc::analyzer_detail::default_init_state(sn, init))
3010 "-a prob reports the probability of the model's DEFAULT INITIAL STATE, and this "
3011 "model's initial marking admits no state; check the class populations against their "
3012 "reference stations");
3020 if (!k.state.empty()) {
3023 "--state is the state of ONE node and needs --node to say which; a bare state "
3024 "vector cannot be matched against a network whose nodes have different widths");
3028 " is not a stateful node, so it holds no state to ask about");
3037 const std::size_t w = d.
chain.space.empty() ? k.state.size()
3038 : d.
chain.space[0].local[isf - 1].size();
3039 if (k.state.size() != w)
3041 "--state has " + std::to_string(k.state.size()) +
" entries but node " +
3042 std::to_string(k.node) +
" encodes its state in " + std::to_string(w) +
3043 "; getProb(node, state) takes the node's whole encoded row, and a shorter one "
3044 "padded with zeros is a different state rather than a partial one (use -a marg "
3045 "for a per-class job-count marginal)");
3047 for (std::size_t i = 0; i < k.state.size(); ++i)
3049 init.
local[isf - 1] = row;
3056 if (g_json_output) {
3058 p[
"type"] =
"ProbAggr";
3062 line::reg::Json st = line::reg::Json::array(), pm = line::reg::Json::array(),
3063 pa = line::reg::Json::array();
3064 for (std::size_t i = 0; i < sn.
nstations; ++i) {
3072 emit_analysis<T>(
"prob", p, d.
actualmethod, ctmc_meta<T>(d));
3075 print_ctmc_banner<T>(d);
3078 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
3079 for (std::size_t i = 0; i < sn.
nstations; ++i)
3080 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
3096 for (std::size_t i = 0; i < filt.size(); ++i)
3097 for (std::size_t r = 0; r < filt[i].size(); ++r) {
3098 line::reg::Json bfrom = line::reg::Json::array(), bto = line::reg::Json::array(),
3099 brate = line::reg::Json::array();
3100 for (std::size_t a = 0; a < n; ++a)
3101 for (std::size_t b = 0; b < n; ++b) {
3103 if (q == 0.0)
continue;
3108 if (bfrom.empty())
continue;
3115 blocks.push_back(e);
3134 const std::size_t n = g.
Q.rows();
3135 std::size_t nnz = 0;
3136 for (std::size_t i = 0; i < n; ++i)
3137 for (std::size_t j = 0; j < n; ++j)
3139 if (g_json_output) {
3141 p[
"type"] =
"InfGen";
3148 line::reg::Json from = line::reg::Json::array(), to = line::reg::Json::array(),
3149 rate = line::reg::Json::array();
3150 for (std::size_t i = 0; i < n; ++i)
3151 for (std::size_t j = 0; j < n; ++j) {
3153 if (q == 0.0)
continue;
3167 for (std::size_t a = 0; a < g.
sync.size() && a < g.
filt.size(); ++a) {
3172 std::size_t fnz = 0;
3173 line::reg::Json ffrom = line::reg::Json::array(), fto = line::reg::Json::array(),
3174 frate = line::reg::Json::array();
3175 for (std::size_t i = 0; i < n; ++i)
3176 for (std::size_t j = 0; j < n; ++j) {
3178 if (q == 0.0)
continue;
3189 e[
"activeNode"] = g.
sync[a].active.node;
3190 e[
"activeClass"] = g.
sync[a].active.cls;
3192 e[
"passiveNode"] = g.
sync[a].passive.node;
3193 e[
"passiveClass"] = g.
sync[a].passive.cls;
3202 p[
"startFilt"] = aux_filt_json<T>(g.
start_filt, n);
3203 p[
"preemptFilt"] = aux_filt_json<T>(g.
preempt_filt, n);
3204 emit_analysis<T>(
"gen", p, d.
actualmethod, ctmc_meta<T>(d));
3207 print_ctmc_banner<T>(d);
3208 std::printf(
"InfGen events=%zu nnz=%zu\n", g.
sync.size(), nnz);
3209 std::printf(
"%8s %8s %16s\n",
"From",
"To",
"Rate");
3210 for (std::size_t i = 0; i < n; ++i)
3211 for (std::size_t j = 0; j < n; ++j) {
3213 if (q != 0.0) std::printf(
"%8zu %8zu %16.10g\n", i + 1, j + 1, q);
3215 std::printf(
"%6s %-10s %6s %6s %-10s %6s %6s %8s\n",
"Event",
"ActEvent",
"ActNode",
"ActCls",
3216 "PasEvent",
"PasNode",
"PasCls",
"Nnz");
3217 for (std::size_t a = 0; a < g.
sync.size() && a < g.
filt.size(); ++a) {
3218 std::size_t fnz = 0;
3219 for (std::size_t i = 0; i < n; ++i)
3220 for (std::size_t j = 0; j < n; ++j)
3222 std::printf(
"%6zu %-10s %6zu %6zu %-10s %6zu %6zu %8zu\n", a + 1,
3225 g.
sync[a].passive.node, g.
sync[a].passive.cls, fnz);
3236 if (g_json_output) {
3238 p[
"type"] =
"StateSpace";
3243 p[
"space"] = matrix_json(s.
flat);
3244 p[
"spaceAggr"] = matrix_json(A);
3252 for (std::size_t f = 0; f < s.
local.size(); ++f) loc.push_back(matrix_json(s.
local[f]));
3253 p[
"localSpace"] = loc;
3257 p[
"pi"] = vector_json(d.
pi);
3258 emit_analysis<T>(
"states", p, d.
actualmethod, ctmc_meta<T>(d));
3261 print_ctmc_banner<T>(d);
3264 std::printf(
"NodeWidths");
3267 std::printf(
"%8s %12s %s\n",
"State",
"Prob",
"Detailed | Aggregate");
3268 for (std::size_t i = 0; i < s.
flat.rows(); ++i) {
3270 for (std::size_t c = 0; c < s.
flat.cols(); ++c)
3273 for (std::size_t c = 0; c < A.
cols(); ++c)
3300 std::vector<bool> queueing(M,
false);
3301 for (std::size_t i = 0; i < M; ++i)
3307 for (std::size_t s = 0; s < reward.size(); ++s)
3308 for (std::size_t i = 0; i < M; ++i)
3310 for (std::size_t c = 0; c < K; ++c) reward[s] += A(s, i * K + c);
3312 std::vector<line::ctmc::CtmcSensParam<T> > params;
3313 std::vector<std::string> skipped;
3314 for (std::size_t i = 0; i < M; ++i) {
3316 for (std::size_t c = 0; c < K; ++c) {
3319 if (!(mu > 0.0))
continue;
3320 const std::string nm =
3323 skipped.push_back(nm);
3333 params.push_back(p);
3338 "-a sens found no exponential service rate to differentiate: every enabled "
3339 "(station, class) pair carries a non-exponential distribution, and replacing one with "
3340 "an exponential of the perturbed rate would change the model rather than a parameter "
3343 const std::vector<line::ctmc::CtmcSensRank<T> > rank =
3345 if (g_json_output) {
3347 p[
"type"] =
"SensRanking";
3352 p[
"reward"] =
"mean number of jobs at the queueing stations";
3353 line::reg::Json par = line::reg::Json::array(), val = line::reg::Json::array(),
3354 S = line::reg::Json::array(), SS = line::reg::Json::array();
3355 for (std::size_t l = 0; l < rank.size(); ++l) {
3356 par.push_back(rank[l].parameter);
3357 val.push_back(rank[l].value);
3361 if (rank[l].scaled_valid)
3366 p[
"Parameter"] = par;
3369 p[
"ScaledSens"] = SS;
3371 for (std::size_t l = 0; l < skipped.size(); ++l) sk.push_back(skipped[l]);
3373 emit_analysis<T>(
"sens", p, d.
actualmethod, ctmc_meta<T>(d));
3376 print_ctmc_banner<T>(d);
3377 std::printf(
"Reward mean number of jobs at the queueing stations\n");
3378 std::printf(
"%-28s %14s %16s %16s\n",
"Parameter",
"Value",
"Sens",
"ScaledSens");
3379 for (std::size_t l = 0; l < rank.size(); ++l) {
3380 if (rank[l].scaled_valid)
3381 std::printf(
"%-28s %14.6g %16.8g %16.8g\n", rank[l].parameter.c_str(), rank[l].value,
3387 std::printf(
"%-28s %14.6g %16.8g %16s\n", rank[l].parameter.c_str(), rank[l].value,
3390 for (std::size_t l = 0; l < skipped.size(); ++l)
3391 std::printf(
"Skipped %s: service is not exponential\n", skipped[l].c_str());
3398 std::vector<std::string> names;
3400 if (g_json_output) {
3402 p[
"type"] =
"AvgReward";
3404 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
3406 p[
"E"] = vector_json(r);
3409 emit_analysis<T>(
"reward", p, std::string());
3413 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
3414 for (std::size_t l = 0; l < r.size(); ++l)
3436 if (k.reward_name.empty())
3438 "-a reward-value returns the value function of ONE reward and needs --reward-name to "
3439 "say which; -a reward returns the steady-state expectation of every declared reward");
3441 std::size_t which = rr.
names.size();
3442 for (std::size_t l = 0; l < rr.
names.size(); ++l)
3443 if (rr.
names[l] == k.reward_name) which = l;
3444 if (which == rr.
names.size()) {
3446 for (std::size_t l = 0; l < rr.
names.size(); ++l)
3447 avail += (l ?
", " :
"") + rr.
names[l];
3449 "' is not declared by this model; it declares: " +
3450 (avail.empty() ? std::string(
"(none)") : avail));
3454 if (g_json_output) {
3456 p[
"type"] =
"RewardValueFunction";
3458 p[
"Reward"] = rr.
names[which];
3459 p[
"steps"] = V.
rows();
3460 p[
"states"] = V.
cols();
3461 p[
"t"] = vector_json(rr.
t);
3462 p[
"V"] = matrix_json(V);
3464 emit_analysis<T>(
"rewardvalue", p, std::string());
3467 std::printf(
"SolverCTMC arith=%s reward=%s steps=%zu states=%zu\n",
3469 std::printf(
"%-10s %-10s %20s\n",
"Step",
"State",
"V");
3470 for (std::size_t i = 0; i < V.
rows(); ++i)
3471 for (std::size_t j = 0; j < V.
cols(); ++j)
3494 (void)sn; (void)opt; (void)k;
3496 "-a tranreward integrates the forward equation, which needs transcendental "
3497 "arithmetic; rerun with --arith double or --arith real");
3501 "-a tranreward integrates E[r(X(t))] and needs a horizon: pass --tspan <t0>:<t1>");
3502 std::vector<std::string> names;
3507 if (g_json_output) {
3509 p[
"type"] =
"TranReward";
3512 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
3514 p[
"t"] = vector_json<T>(t);
3516 for (std::size_t l = 0; l < r.size(); ++l) e.push_back(vector_json<T>(r[l]));
3518 emit_analysis<T>(
"tranreward", p, std::string());
3521 std::printf(
"SolverCTMC arith=%s rewards=%zu points=%zu tspan=[%g,%g]\n",
3523 std::printf(
"%-16s",
"t");
3524 for (std::size_t l = 0; l < names.size(); ++l) std::printf(
" %16s", names[l].c_str());
3526 for (std::size_t i = 0; i < t.size(); ++i) {
3528 for (std::size_t l = 0; l < r.size(); ++l)
3550 "-a tranprob integrates pi(t) and needs a horizon: pass --tspan <t0>:<t1>");
3559 if (g_json_output) {
3561 p[
"type"] =
"TranProb";
3563 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
3564 if (k.node) p[
"node"] = k.node - 1;
3566 span.push_back(k.t0);
3567 span.push_back(k.t1);
3572 p[
"t"] = vector_json(det.
t);
3577 p[
"labels"] = matrix_json(det.
labels);
3578 p[
"labelsAggr"] = matrix_json(agg.
labels);
3579 p[
"pit"] = matrix_json(det.
pit);
3580 p[
"pitAggr"] = matrix_json(agg.
pit);
3581 emit_analysis<T>(
"tranprob", p, tr.chain.actualmethod, ctmc_meta<T>(tr.chain));
3584 print_ctmc_banner<T>(tr.chain);
3585 std::printf(
"TranProb times=%zu tspan=%g:%g scope=%s\n", det.
t.size(), k.t0, k.t1,
3586 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
3589 std::printf(
"%8s %s\n",
"State",
"Detailed | Aggregate");
3590 for (std::size_t s = 0; s < det.
labels.rows(); ++s) {
3591 std::printf(
"%8zu ", s + 1);
3592 for (std::size_t c = 0; c < det.
labels.cols(); ++c)
3595 for (std::size_t c = 0; c < agg.
labels.cols(); ++c)
3599 std::printf(
"%14s",
"Time");
3600 for (std::size_t s = 0; s < det.
pit.cols(); ++s) std::printf(
" %12zu", s + 1);
3602 for (std::size_t i = 0; i < det.
t.size(); ++i) {
3604 for (std::size_t s = 0; s < det.
pit.cols(); ++s)
3633 "-a tran integrates the forward equation and needs a horizon: pass --tspan <t0>:<t1>");
3638 if (g_json_output) {
3640 p[
"type"] =
"TranAvgTable";
3647 for (std::size_t i = 0; i < M; ++i)
3648 for (std::size_t c = 0; c < K; ++c) {
3655 e[
"Station"] = sn.
stations[i].name;
3656 e[
"JobClass"] = sn.
classes[c].name;
3660 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
3661 x = line::reg::Json::array();
3662 for (std::size_t j = 0; j < nt; ++j) {
3673 emit_analysis<T>(
"tran", p, tr.chain.actualmethod, ctmc_meta<T>(tr.chain));
3676 print_ctmc_banner<T>(tr.chain);
3677 std::printf(
"TranAvg times=%zu tspan=%g:%g\n", nt, k.t0, k.t1);
3678 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
3680 for (std::size_t i = 0; i < M; ++i)
3681 for (std::size_t c = 0; c < K; ++c) {
3683 for (std::size_t j = 0; j < nt; ++j)
3684 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
3708 const std::size_t nevents = k.events ? k.events : (k.samples ? k.samples : 1000);
3709 const unsigned long seed = k.seed ? k.seed : 23000;
3718 if (g_json_output) {
3720 p[
"type"] =
"SamplePath";
3724 p[
"events"] = nevents;
3726 p[
"drawn"] = path.
state.size();
3727 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
3728 if (k.node) p[
"node"] = k.node - 1;
3729 p[
"t"] = vector_json(path.
t);
3730 p[
"state"] = index_json(path.
state);
3732 for (std::size_t i = 0; i < path.
state.size(); ++i) {
3736 if (i < path.
event.size() && path.
event[i] !=
static_cast<std::size_t
>(-1))
3737 ev.push_back(path.
event[i]);
3742 p[
"sysAggr"] = matrix_json(A);
3750 p[
"space"] = matrix_json(s.
flat);
3754 p[
"nodeState"] = matrix_json(L);
3755 p[
"nodeAggr"] = matrix_json(LA);
3757 emit_analysis<T>(
"sample", p, path.
chain.actualmethod, ctmc_meta<T>(path.
chain));
3762 std::printf(
"SolverCTMC arith=%s states=%zu events=%zu seed=%lu drawn=%zu scope=%s\n",
3764 path.
state.size(), k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
3765 std::printf(
"%14s %8s %8s %s\n",
"Time",
"State",
"Event",
3766 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
3767 for (std::size_t i = 0; i < path.
state.size(); ++i) {
3768 const std::size_t ev = i < path.
event.size() ? path.
event[i] :
static_cast<std::size_t
>(-1);
3770 if (ev ==
static_cast<std::size_t
>(-1))
3771 std::printf(
"%8s ",
"absorb");
3773 std::printf(
"%8zu ", ev + 1);
3774 for (std::size_t c = 0; c < A.
cols(); ++c)
3778 for (std::size_t c = 0; c < L.
cols(); ++c)
3781 for (std::size_t c = 0; c < LA.
cols(); ++c)
3792 const std::vector<std::vector<line::ctmc::CdfCurve<T> > > RD =
3795 if (g_json_output) {
3797 p[
"type"] =
"CdfRespT";
3804 for (std::size_t i = 0; i < RD.size(); ++i)
3805 for (std::size_t c = 0; c < RD[i].size(); ++c) {
3809 if (RD[i][c].empty())
continue;
3811 e[
"Station"] = sn.
stations[i].name;
3812 e[
"JobClass"] = sn.
classes[c].name;
3815 e[
"t"] = vector_json(RD[i][c].t);
3816 e[
"F"] = vector_json(RD[i][c].F);
3821 for (std::size_t c = 0; c < RS.size(); ++c) {
3822 if (RS[c].empty())
continue;
3825 e[
"t"] = vector_json(RS[c].t);
3826 e[
"F"] = vector_json(RS[c].F);
3834 emit_analysis<T>(
"cdf", p, std::string());
3838 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
3839 for (std::size_t i = 0; i < RD.size(); ++i)
3840 for (std::size_t c = 0; c < RD[i].size(); ++c) {
3843 if (RD[i][c].empty())
continue;
3844 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
3845 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
3850 std::printf(
"%-16s %-14s %14s %14s\n",
"System",
"Chain",
"Time",
"F(t)");
3851 for (std::size_t c = 0; c < RS.size(); ++c) {
3852 if (RS[c].empty())
continue;
3853 for (std::size_t j = 0; j < RS[c].t.size(); ++j)
3854 std::printf(
"%-16s %-14zu %14.8g %14.10g\n",
"(system)", c + 1,
3869 std::vector<std::vector<double>> rows;
3870 std::size_t pos = 0;
3871 while (pos <= spec.size()) {
3872 std::size_t semi = spec.find(
';', pos);
3873 if (semi == std::string::npos) semi = spec.size();
3874 std::string rowtxt = spec.substr(pos, semi - pos);
3875 std::vector<double> row;
3877 while (p2 <= rowtxt.size()) {
3878 std::size_t comma = rowtxt.find(
',', p2);
3879 if (comma == std::string::npos) comma = rowtxt.size();
3880 std::string cell = rowtxt.substr(p2, comma - p2);
3881 if (!cell.empty()) {
3883 const double v = std::strtod(cell.c_str(), &endp);
3884 if (endp == cell.c_str() || *endp !=
'\0')
3886 "' is not a number");
3891 if (!row.empty()) rows.push_back(row);
3895 for (std::size_t i = 1; i < rows.size(); ++i)
3896 if (rows[i].size() != rows[0].size())
3898 ": every state row must have the same width");
3900 for (std::size_t i = 0; i < rows.size(); ++i)
3901 for (std::size_t j = 0; j < rows[i].size(); ++j) out(i, j) = rows[i][j];
3910std::vector<double> parse_passage_tset(
const std::string& spec) {
3911 std::vector<double> t;
3912 std::size_t pos = 0;
3913 while (pos <= spec.size()) {
3914 std::size_t comma = spec.find(
',', pos);
3915 if (comma == std::string::npos) comma = spec.size();
3916 std::string cell = spec.substr(pos, comma - pos);
3917 const std::size_t b = cell.find_first_not_of(
" \t");
3918 const std::size_t e = cell.find_last_not_of(
" \t");
3919 cell = (b == std::string::npos) ? std::string() : cell.substr(b, e - b + 1);
3922 "'; give the times as 't1,t2,...', 'inf' allowed");
3923 char* endp =
nullptr;
3924 const double v = std::strtod(cell.c_str(), &endp);
3925 if (endp == cell.c_str() || *endp !=
'\0' || std::isnan(v))
3943 if (k.passage_into.empty())
3945 "-a firstpasst times the passage INTO a state set and needs --passage-into; name it "
3946 "as 1-based rows of the state space ('3,5') or as state rows ('0,2;1,1')");
3950 const std::vector<double> tset =
3951 k.passage_tset.empty() ? std::vector<double>() : parse_passage_tset(k.passage_tset);
3952 const std::string method = k.passage_method.empty() ?
"expm" : k.passage_method;
3957 if (g_json_output) {
3959 p[
"type"] =
"CdfFirstPassT";
3964 line::reg::Json src = line::reg::Json::array(), tgt = line::reg::Json::array();
3965 for (std::size_t i : fp.source) src.push_back(
static_cast<double>(i));
3966 for (std::size_t i : fp.target) tgt.push_back(
static_cast<double>(i));
3970 for (std::size_t i : fp.taboo) tab.push_back(
static_cast<double>(i));
3972 p[
"reachprob"] = fp.reachprob;
3973 p[
"tabooprob"] = fp.tabooprob;
3974 emit_analysis<T>(
"firstpasst", p, method);
3977 std::printf(
"SolverCTMC arith=%s method=%s getCdfFirstPassT\n",
3979 std::printf(
"source states: %zu%s, target states: %zu, taboo states: %zu\n",
3980 fp.source.size(), fp.source.empty() ?
" (conditional stationary law)" :
"",
3981 fp.target.size(), fp.taboo.size());
3982 std::printf(
"P(target first) = %.10g, P(taboo first) = %.10g\n", fp.reachprob,
3984 std::printf(
"%14s %14s %14s\n",
"Time",
"F(t)",
"f(t)");
3985 for (std::size_t j = 0; j < fp.t.size(); j += 111)
3986 std::printf(
"%14.8g %14.10g %14.10g\n", fp.t[j], fp.F[j], fp.f[j]);
3987 std::printf(
"%14.8g %14.10g %14.10g\n", fp.t.back(), fp.F.back(), fp.f.back());
4003 if (k.passage_into.empty())
4005 "-a firstpasstmom times the passage INTO a state set and needs --passage-into; name "
4006 "it as 1-based rows of the state space ('3,5') or as state rows ('0,2;1,1')");
4010 const std::size_t nmax = (k.passage_orders > 0) ? k.passage_orders : 3;
4015 if (g_json_output) {
4017 p[
"type"] =
"FirstPassTMoments";
4020 for (std::size_t i = 0; i < fm.
m.size(); ++i)
4024 for (std::size_t i = 0; i < fm.
mall.rows(); ++i) {
4026 for (std::size_t j = 0; j < fm.
mall.cols(); ++j)
4028 mall.push_back(row);
4031 line::reg::Json src = line::reg::Json::array(), tgt = line::reg::Json::array();
4032 for (std::size_t i : fm.
source) src.push_back(
static_cast<double>(i));
4033 for (std::size_t i : fm.
target) tgt.push_back(
static_cast<double>(i));
4036 line::reg::Json tab = line::reg::Json::array(), pr = line::reg::Json::array();
4037 for (std::size_t i : fm.
taboo) tab.push_back(
static_cast<double>(i));
4038 for (std::size_t i = 0; i < fm.
p.size(); ++i)
4042 emit_analysis<T>(
"firstpasstmom", p,
"moments");
4046 std::printf(
"source states: %zu%s, target states: %zu, taboo states: %zu\n",
4047 fm.
source.size(), fm.
source.empty() ?
" (conditional stationary law)" :
"",
4049 if (!fm.
taboo.empty())
4050 std::printf(
"moments are CONDITIONAL on entering the target before the taboo set\n");
4051 std::printf(
"%8s %20s\n",
"Order",
"Moment");
4052 for (std::size_t i = 0; i < fm.
m.size(); ++i)
4062std::vector<line::ctmc::CtmcRateSched> parse_rate_sched(
const std::string& arg,
4064 std::string text = arg;
4065 const std::size_t first = arg.find_first_not_of(
" \t\r\n");
4066 if (first == std::string::npos || (arg[first] !=
'[' && arg[first] !=
'{')) {
4067 std::ifstream
in(arg.c_str());
4070 "' is neither inline JSON nor a readable file");
4071 std::ostringstream ss;
4075 line::io::detail::json root;
4077 root = line::io::detail::json::parse(text);
4078 }
catch (
const std::exception& e) {
4079 throw line::InputError(std::string(
"--rate-sched: not valid JSON: ") + e.what());
4081 if (root.is_object()) root = line::io::detail::json::array({root});
4082 if (!root.is_array() || root.empty())
4083 throw line::InputError(
"--rate-sched: expected a non-empty array of schedule objects");
4085 const auto resolve = [](
const line::io::detail::json& v,
const std::vector<std::string>& names,
4086 const char* what) -> std::size_t {
4087 if (v.is_number_integer()) {
4088 const long long i = v.get<
long long>();
4089 if (i < 1 ||
static_cast<std::size_t
>(i) > names.size())
4091 std::to_string(i) +
" is out of range (1-based)");
4092 return static_cast<std::size_t
>(i);
4094 if (v.is_string()) {
4095 const std::string nm = v.get<std::string>();
4096 for (std::size_t i = 0; i < names.size(); ++i)
4097 if (names[i] == nm)
return i + 1;
4098 throw line::InputError(std::string(
"--rate-sched: no ") + what +
" named '" + nm +
"'");
4101 " must be a name or a 1-based index");
4103 std::vector<std::string> stations, classes;
4104 for (std::size_t i = 0; i < sn.
stations.size(); ++i)
4106 for (
const auto& c : sn.
classes) classes.push_back(c.name);
4108 std::vector<line::ctmc::CtmcRateSched> out;
4109 for (
const auto& e : root) {
4110 if (!e.is_object() || !e.contains(
"station") || !e.contains(
"class") ||
4111 !e.contains(
"tgrid") || !e.contains(
"rates"))
4113 "--rate-sched: every entry needs station, class, tgrid and rates");
4115 rs.
station = resolve(e.at(
"station"), stations,
"station");
4116 rs.
cls = resolve(e.at(
"class"), classes,
"class");
4117 rs.
tgrid = e.at(
"tgrid").get<std::vector<double>>();
4118 rs.
rates = e.at(
"rates").get<std::vector<double>>();
4119 if (e.contains(
"nominal") && !e.at(
"nominal").is_null())
4120 rs.
nominal = e.at(
"nominal").get<
double>();
4143int solve_model_ctmc(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
4146 if (!k.method.empty()) opt.
method = k.method;
4147 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4149 opt.
force = k.force;
4150 if (!k.nonmkv.empty()) opt.
nonmkv = k.nonmkv;
4151 if (k.timestep > 0.0) opt.
timestep = k.timestep;
4154 if (!k.transient_method.empty()) opt.
transient_method = k.transient_method;
4155 if (k.fau_epsilon > 0.0) opt.
fau_epsilon = k.fau_epsilon;
4156 if (k.fau_delta >= 0.0) opt.
fau_delta = k.fau_delta;
4157 if (k.ctmc_tv_ngrid > 0) opt.
ctmc_tv_ngrid = k.ctmc_tv_ngrid;
4159 if (!k.rate_sched.empty()) opt.
rate_sched = parse_rate_sched(k.rate_sched, sn);
4165 if (opt.
method ==
"mdd") {
4167 if (k.mdd_tol > 0.0) mcdopt.
tol = k.mdd_tol;
4168 if (k.mdd_maxiter > 0) mcdopt.
maxiter = k.mdd_maxiter;
4169 return solve_ctmc_mdd_avg<T>(sn, opt, mcdopt);
4174 if (analysis ==
"avg")
return solve_ctmc_avg<T>(sn, opt);
4175 if (analysis ==
"prob")
return solve_ctmc_prob<T>(sn, opt, k);
4176 if (analysis ==
"gen")
return solve_ctmc_gen<T>(sn, opt);
4177 if (analysis ==
"states")
return solve_ctmc_states<T>(sn, opt);
4178 if (analysis ==
"sens")
return solve_ctmc_sens<T>(sn, opt);
4179 if (analysis ==
"reward")
return solve_ctmc_reward<T>(sn, opt);
4180 if (analysis ==
"rewardvalue")
return solve_ctmc_reward_value<T>(sn, opt, k);
4188 "the -a " + analysis +
4189 " analysis integrates the forward equation, draws exponential clocks or takes a matrix "
4190 "exponential, none of which exists in exact rational arithmetic; rerun with --arith "
4191 "double or --arith real");
4193 if (analysis ==
"tran")
return solve_ctmc_tran<T>(sn, opt, k);
4194 if (analysis ==
"tranprob")
return solve_ctmc_tranprob<T>(sn, opt, k);
4195 if (analysis ==
"tranreward")
return solve_ctmc_tran_reward<T>(sn, opt, k);
4196 if (analysis ==
"sample")
return solve_ctmc_sample<T>(sn, opt, k);
4197 if (analysis ==
"firstpasst")
return solve_ctmc_firstpasst<T>(sn, opt, k);
4198 if (analysis ==
"firstpasstmom")
return solve_ctmc_firstpasst_moments<T>(sn, opt, k);
4199 return solve_ctmc_cdf<T>(sn, opt);
4219int solve_model_ssa_prob(
const std::string& file,
const Knobs& k) {
4228 if (k.samples) opt.
samples = k.samples;
4229 if (k.seed) opt.
seed = k.seed;
4230 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4231 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4234 if (g_json_output) {
4236 p[
"type"] =
"ProbAggr";
4242 p[
"ProbSysSeen"] = r.
sys.
seen;
4244 line::reg::Json st = line::reg::Json::array(), pm = line::reg::Json::array(),
4245 pa = line::reg::Json::array(), sm = line::reg::Json::array();
4246 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4248 pm.push_back(r.
marg[i].prob);
4249 pa.push_back(r.
aggr[i].prob);
4250 sm.push_back(r.
marg[i].seen);
4256 emit_analysis<T>(
"prob", p,
"serial");
4259 std::printf(
"SolverSSA arith=%s method=serial samples=%zu seed=%lu time=%.6g\n",
4261 std::printf(
"ProbSys = %.8g%s\n", r.
sys.
prob, r.
sys.
seen ?
"" :
" (state never visited)");
4264 std::printf(
"%-20s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
4265 for (std::size_t i = 0; i < sn.
nstations; ++i)
4266 std::printf(
"%-20s %14.8g %14.8g\n", sn.
stations[i].name.c_str(), r.
marg[i].prob,
4284int solve_model_ssa_sample(
const std::string& file,
const Knobs& k) {
4289 opt.
samples = k.events ? k.events : (k.samples ? k.samples : 1000);
4290 if (k.seed) opt.
seed = k.seed;
4291 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4292 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4300 if (g_json_output) {
4302 p[
"type"] =
"SamplePath";
4305 p[
"seed"] = sys.
seed;
4306 p[
"drawn"] = sys.
t.size();
4307 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
4308 if (k.node) p[
"node"] = k.node - 1;
4309 p[
"t"] = vector_json(sys.
t);
4310 p[
"event"] = index_json(sys.
event);
4311 p[
"state"] = matrix_json(sys.
state);
4320 w.push_back(sim.
run.space.empty() ? 0 : sim.
run.space[0].local[f].size());
4321 p[
"NodeWidths"] = w;
4323 p[
"sysAggr"] = matrix_json(sys.
aggr);
4325 p[
"nodeState"] = matrix_json(nodep.
state);
4326 p[
"nodeAggr"] = matrix_json(nodep.
aggr);
4328 emit_analysis<T>(
"sample", p,
"serial");
4331 std::printf(
"SolverSSA arith=%s method=serial events=%zu seed=%lu drawn=%zu scope=%s\n",
4333 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
4334 std::printf(
"%14s %8s %s\n",
"Time",
"Event",
4335 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
4336 for (std::size_t i = 0; i < sys.
t.size(); ++i) {
4337 std::printf(
"%14.8g %8zu ", sys.
t[i], sys.
event[i]);
4338 for (std::size_t c = 0; c < sys.
aggr.cols(); ++c)
4342 for (std::size_t c = 0; c < nodep.
state.cols(); ++c)
4345 for (std::size_t c = 0; c < nodep.
aggr.cols(); ++c)
4365 for (std::size_t i = 0; i < m.
rows(); ++i)
4366 for (std::size_t j = 0; j < m.
cols(); ++j)
4392int solve_model_ssa(
const std::string& file,
const Knobs& k) {
4395 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4396 if (k.samples) opt.
samples = k.samples;
4397 if (k.seed) opt.
seed = k.seed;
4398 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4399 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4405 std::vector<line::ssa::SsaCacheRatio> cacheratio;
4417 std::printf(
"SolverSSA arith=%s method=%s type=%s samples=%zu seed=%lu time=%.6g\n",
4438 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
4448 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
4453 row.w = line::num_traits<T>::to_double(WN(i, c));
4457 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
4459 : line::num_traits<T>::to_double(AN(i, c));
4469 if (!k.method.empty()) opt.
method = k.method;
4470 if (k.tol >= 0.0) opt.
tol = k.tol;
4471 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4472 if (k.iter_max >= 0) opt.
iter_max =
static_cast<std::size_t
>(k.iter_max);
4474 if (!k.nonmkv.empty()) opt.
nonmkv = k.nonmkv;
4479 if (k.pstar > 0.0) {
4480 opt.
pstar = k.pstar;
4483 if (!k.fluid_boundary.empty()) opt.
fluid_boundary = k.fluid_boundary;
4501int solve_model_fluid(
const std::string& file,
const Knobs& k) {
4519 std::printf(
"SolverFluid arith=%s method=%s type=%s iters=%d\n",
4541 &refreshed, &cache);
4550 if (!r.
warning.empty()) std::fprintf(stderr,
"Warning: %s\n", r.
warning.c_str());
4564 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
4567 if (!fjb.is_null()) extra[
"FJ"] = fjb;
4569 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
4574 row.w = line::num_traits<T>::to_double(WN(i, c));
4580 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
4582 : line::num_traits<T>::to_double(AN(i, c));
4603int solve_model_fluid_statevec(
const std::string& file,
const Knobs& k) {
4610 "-a statevec reports the converged ODE state and this solve produced none; the rmf "
4611 "and closing branches integrate a drift and fill it, so a branch that returns means "
4612 "directly has no state vector to report");
4616 std::vector<std::size_t> ist, cls, phs;
4617 for (std::size_t i = 0; i < sn.
nstations; ++i)
4618 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4619 const std::size_t np = sn.
phases_of(i + 1, c + 1);
4620 for (std::size_t j = 0; j < np; ++j) {
4629 const bool labelled = ist.size() == r.
xvec.size();
4631 if (g_json_output) {
4633 p[
"type"] =
"FluidStateVec";
4635 p[
"xvec"] = vector_json(r.
xvec);
4636 p[
"labelled"] = labelled;
4638 line::reg::Json st = line::reg::Json::array(), cl = line::reg::Json::array(),
4639 ph = line::reg::Json::array();
4640 for (std::size_t j = 0; j < ist.size(); ++j) {
4641 st.push_back(sn.
stations[ist[j]].name);
4642 cl.push_back(sn.
classes[cls[j]].name);
4643 ph.push_back(phs[j]);
4649 emit_analysis<T>(
"statevec", p, r.
method);
4655 std::printf(
"# the ODE state is %zu wide and the (station, class, phase) layout accounts "
4656 "for %zu; the coordinates are printed unlabelled\n",
4657 r.
xvec.size(), ist.size());
4658 std::printf(
"%-10s %20s\n",
"Index",
"x");
4659 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
4660 std::printf(
"%-10zu %20.10g\n", j, r.
xvec[j]);
4663 std::printf(
"%-16s %-14s %-8s %20s\n",
"Station",
"JobClass",
"Phase",
"x");
4664 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
4665 std::printf(
"%-16s %-14s %-8zu %20.10g\n", sn.
stations[ist[j]].name.c_str(),
4666 sn.
classes[cls[j]].name.c_str(), phs[j], r.
xvec[j]);
4671inline std::vector<double> parse_env_init(
const std::string& spec) {
4672 std::vector<double> p;
4673 std::size_t pos = 0;
4674 while (pos <= spec.size()) {
4675 std::size_t comma = spec.find(
',', pos);
4676 if (comma == std::string::npos) comma = spec.size();
4677 const std::string cell = spec.substr(pos, comma - pos);
4678 char* endp =
nullptr;
4679 const double v = std::strtod(cell.c_str(), &endp);
4680 if (cell.empty() || endp == cell.c_str() || *endp !=
'\0' || !std::isfinite(v))
4682 "' is not a probability; give one per stage, as 'p1,p2,...'");
4690inline std::string env_vector_text(
const std::vector<double>& v) {
4691 std::string s =
"[";
4693 for (std::size_t i = 0; i < v.size(); ++i) {
4694 std::snprintf(buf,
sizeof buf,
"%s%.6g", i ?
" " :
"", v[i]);
4718 line::env::dispatch_detail::env_take_statevec(out);
4745int solve_model_env(
const std::string& file,
const Knobs& k) {
4748 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4749 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4750 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4752 if (k.tran_points) opt.
tran_points = k.tran_points;
4753 if (k.tol >= 0.0) opt.
stage.
tol = k.tol;
4771 if (!k.stage_solver.empty()) opt.
stage_solver = k.stage_solver;
4779 if (!k.env_init.empty()) opt.
env_init = parse_env_init(k.env_init);
4799 std::printf(
"SolverENV arith=%s method=%s stages=%zu (closed-form limit)\n",
4801 else if (r.
method ==
"occupation")
4804 std::printf(
"SolverENV arith=%s method=%s stages=%zu occupation=%s weight=%s\n",
4808 else if (r.
method ==
"statevec")
4809 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g iters=%d%s\n",
4816 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g points=%zu iters=%d%s\n",
4824 if (!r.
cache.empty()) extra[
"Cache"] = cache_extra_json<T>(r.
cache);
4825 if (r.
method ==
"occupation") {
4832 [&](std::size_t i, std::size_t c) {
4834 row.q = line::num_traits<T>::to_double(r.QN(i, c));
4835 row.u = line::num_traits<T>::to_double(r.UN(i, c));
4836 row.t = line::num_traits<T>::to_double(r.TN(i, c));
4837 row.r = std::numeric_limits<double>::quiet_NaN();
4838 row.a = std::numeric_limits<double>::quiet_NaN();
4839 row.w = row.q / row.t;
4843 if (!g_json_output) print_cache_rows<T>(sn, r.
cache);
4860int solve_model_fluid_var(
const std::string& file,
const Knobs& k) {
4867 "-a var needs a fluid method that computes a second moment: 'minnormal', 'refined' or "
4868 "'dae' for the stationary covariance, 'kp' for the covariance along the trajectory. "
4870 r.
method +
"' method integrates the mean only");
4872 if (g_json_output) {
4874 p[
"type"] =
"QueueLengthVariance";
4876 p[
"Station"] = line::reg::Json::array();
4877 p[
"JobClass"] = line::reg::Json::array();
4878 p[
"QVar"] = line::reg::Json::array();
4879 p[
"QStd"] = line::reg::Json::array();
4880 for (std::size_t i = 0; i < sn.
nstations; ++i)
4881 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4883 p[
"Station"].push_back(sn.
stations[i].name);
4884 p[
"JobClass"].push_back(sn.
classes[c].name);
4889 emit_analysis<T>(
"var", p, r.
method);
4894 std::printf(
"%-16s %-14s %12s %12s\n",
"Station",
"JobClass",
"QVar",
"QStd");
4895 for (std::size_t i = 0; i < sn.
nstations; ++i)
4896 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4898 std::printf(
"%-16s %-14s %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4914int solve_model_fluid_odes(
const std::string& file,
const Knobs& k) {
4917 if (!k.method.empty()) opt.
method = k.method;
4918 if (k.tol >= 0.0) opt.
tol = k.tol;
4925 const std::string notation = k.notation.empty() ?
"scalar" : k.notation;
4927 if (g_json_output) {
4930 p[
"notation"] = notation;
4936 emit_analysis<T>(
"odes", p, (opt.
method.empty() || opt.
method ==
"default") ?
"matrix" : opt.
method);
4939 std::printf(
"%s\n", tex.c_str());
4955int solve_model_fluid_jacobian(
const std::string& file,
const Knobs& k) {
4958 if (!k.method.empty()) opt.
method = k.method;
4965 std::string m = opt.
method;
4966 if (m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
4970 if (!k.symbolic.empty()) symopt.
backend = k.symbolic;
4974 if (g_json_output) {
4976 p[
"type"] =
"Jacobian";
4977 p[
"engine"] = jac.
engine;
4981 for (std::size_t i = 0; i < jac.
J.size(); ++i) rows.push_back(
line::reg::Json(jac.
J[i]));
4982 p[
"jacobian"] = rows;
4987 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e) {
4989 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4991 one[it->first] = it->second;
4994 p[
"equilibria"] = eqs;
4995 emit_analysis<T>(
"jacobian", p,
4996 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
5000 std::printf(
"engine=%s states=%zu\n", jac.
engine.c_str(), jac.
vars.size());
5001 for (std::size_t i = 0; i < jac.
rhs.size(); ++i)
5002 std::printf(
"d%s/dt = %s\n", jac.
vars[i].c_str(), jac.
rhs[i].c_str());
5003 for (std::size_t i = 0; i < jac.
J.size(); ++i)
5004 for (std::size_t j = 0; j < jac.
J[i].size(); ++j) {
5007 std::printf(
"J[%s,%s] = %s\n", jac.
vars[i].c_str(), jac.
vars[j].c_str(),
5008 jac.
J[i][j].c_str());
5012 std::printf(
"equilibria: none in closed form (the solve found none, which is not a "
5013 "proof that none exist)\n");
5014 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e)
5015 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
5017 std::printf(
"equilibrium %zu: %s = %s\n", e + 1, it->first.c_str(),
5018 it->second.c_str());
5035int solve_model_fluid_tranvar(
const std::string& file,
const Knobs& k) {
5043 if (g_json_output) {
5045 p[
"type"] =
"TranAvgVarTable";
5047 p[
"t0"] = tr.t.front();
5048 p[
"t1"] = tr.t.back();
5050 for (std::size_t j = 0; j < tr.t.size(); ++j) ts.push_back(tr.t[j]);
5053 for (std::size_t i = 0; i < sn.
nstations; ++i)
5054 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5057 e[
"Station"] = sn.
stations[i].name;
5058 e[
"JobClass"] = sn.
classes[c].name;
5062 for (std::size_t j = 0; j < tr.QVar.size(); ++j) v.push_back(tr.QVar[j](i, c));
5071 for (std::size_t j = 0; j < tr.Sigma.size(); ++j)
5072 sig.push_back(matrix_json<double>(tr.Sigma[j]));
5074 emit_analysis<T>(
"tranvar", p,
"kp");
5077 std::printf(
"SolverFluid arith=%s method=kp tspan=[%g,%g] points=%zu dim=%zu\n",
5079 tr.Sigma.empty() ? std::size_t(0) : tr.Sigma.front().rows());
5080 std::printf(
"%-16s %-14s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QVar",
"QStd");
5081 for (std::size_t i = 0; i < sn.
nstations; ++i)
5082 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5084 for (std::size_t j = 0; j < tr.QVar.size(); ++j) {
5085 const double var = tr.QVar[j](i, c);
5086 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
5087 sn.
classes[c].name.c_str(), tr.t[j], var,
5088 var >= 0.0 ? std::sqrt(var) : std::numeric_limits<double>::quiet_NaN());
5113int solve_model_fluid_tran(
const std::string& file,
const Knobs& k) {
5117 const std::vector<line::fluid::FluidTranPoint> tr =
5121 const std::string tran_method =
5122 line::fluid::detail::fluid_unqualify(opt.
method) ==
"dae" ? std::string(
"dae") : std::string(
"closing");
5124 if (g_json_output) {
5126 p[
"type"] =
"TranAvgTable";
5129 p[
"t1"] = tr.back().t;
5131 for (std::size_t j = 0; j < tr.size(); ++j) ts.push_back(tr[j].t);
5133 for (std::size_t i = 0; i < sn.
nstations; ++i)
5134 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5137 e[
"Station"] = sn.
stations[i].name;
5138 e[
"JobClass"] = sn.
classes[c].name;
5142 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
5143 x = line::reg::Json::array();
5144 for (std::size_t j = 0; j < tr.size(); ++j) {
5145 q.push_back(tr[j].
QN(i, c));
5146 u.push_back(tr[j].
UN(i, c));
5147 x.push_back(tr[j].TN(i, c));
5155 emit_analysis<T>(
"tran", p, tran_method);
5158 std::printf(
"SolverFluid arith=%s method=%s tspan=[0,%g] points=%zu\n",
5160 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
5162 for (std::size_t i = 0; i < sn.
nstations; ++i)
5163 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5165 for (std::size_t j = 0; j < tr.size(); ++j)
5166 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
5168 tr[j].QN(i, c), tr[j].UN(i, c), tr[j].TN(i, c));
5188int solve_model_fluid_prob(
const std::string& file,
const Knobs& k) {
5195 for (std::size_t i = 0; i < sn.
nstations; ++i)
5198 if (g_json_output) {
5200 p[
"type"] =
"ProbAggr";
5202 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
5203 lp = line::reg::Json::array();
5204 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5206 pa.push_back(pr[i]);
5207 lp.push_back(lg[i]);
5211 p[
"logProbAggr"] = lp;
5212 emit_analysis<T>(
"prob", p, r.
method);
5217 std::printf(
"%-16s %14s %14s\n",
"Station",
"ProbAggr",
"logProbAggr");
5218 for (std::size_t i = 0; i < sn.
nstations; ++i)
5219 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(), pr[i], lg[i]);
5234int solve_model_fluid_cdf(
const std::string& file,
const Knobs& k) {
5238 const std::vector<std::vector<line::fluid::FluidPassage> > RD =
5241 if (g_json_output) {
5243 p[
"type"] =
"CdfRespT";
5246 for (std::size_t i = 0; i < RD.size(); ++i)
5247 for (std::size_t c = 0; c < RD[i].size(); ++c) {
5251 if (RD[i][c].t.empty())
continue;
5253 e[
"Station"] = sn.
stations[i].name;
5254 e[
"JobClass"] = sn.
classes[c].name;
5262 emit_analysis<T>(
"cdf", p, std::string());
5266 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5267 for (std::size_t i = 0; i < RD.size(); ++i)
5268 for (std::size_t c = 0; c < RD[i].size(); ++c)
5269 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
5270 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
5271 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].cdf[j]);
5290int solve_model_fluid_aoi(
const std::string& file,
const Knobs& k) {
5296 "-a aoi reports the age of a status-update system and needs the topology the age laws "
5297 "are defined for: " +
5298 (top.
error.empty() ? std::string(
"this model is not one") : top.
error));
5300 const std::string requested = line::fluid::detail::fluid_unqualify(opt.
method);
5301 if (requested !=
"default" && requested !=
"mfq")
5303 "-a aoi is the AoI branch of the 'mfq' method; '" + requested +
5304 "' integrates the mean-field drift and carries no age process");
5309 "-a aoi: the 'mfq' method did not take its AoI branch on this model");
5314 const std::size_t np = 200;
5315 std::vector<double> tv(np), fa(np), fp(np);
5316 for (std::size_t j = 0; j < np; ++j) {
5317 tv[j] = 5.0 * base *
static_cast<double>(j) /
static_cast<double>(np - 1);
5321 const double asd = std::sqrt(std::max(0.0, r.
aoi.
aoi.
var));
5322 const double psd = std::sqrt(std::max(0.0, r.
aoi.
paoi.
var));
5324 if (g_json_output) {
5345 p[
"AoI_A"] = matrix_json(r.
aoi.
aoi.
A);
5348 p[
"PAoI_A"] = matrix_json(r.
aoi.
paoi.
A);
5350 emit_analysis<T>(
"aoi", p, r.
method);
5353 std::printf(
"SolverFluid arith=%s method=mfq system=%s preemption=%.6g\n",
5355 std::printf(
"%-8s %14s %14s %14s\n",
"Metric",
"Mean",
"Var",
"Std");
5358 std::printf(
"%14s %14s %14s\n",
"Time",
"F_AoI(t)",
"F_PAoI(t)");
5359 for (std::size_t j = 0; j < np; ++j)
5360 std::printf(
"%14.8g %14.10g %14.10g\n", tv[j], fa[j], fp[j]);
5371int solve_model_prob(
const std::string& file,
const Knobs& k) {
5374 "the -a prob analysis fits a binomial/product-form law and needs transcendental "
5375 "arithmetic; rerun with --arith double or --arith real");
5379 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5380 if (k.tol >= 0.0) opt.
tol = k.tol;
5381 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5382 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5388 if (g_json_output) {
5390 p[
"type"] =
"ProbAggr";
5393 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array();
5394 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5408 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
5409 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5412 std::printf(
"%-16s %14.10g\n", sn.
stations[i].name.c_str(),
5436int solve_model_marg(
const std::string& file,
const Knobs& k) {
5439 "the -a marg analysis fits a binomial / Poisson / geometric law and needs "
5440 "transcendental arithmetic; rerun with --arith double or --arith real");
5444 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5445 if (k.tol >= 0.0) opt.
tol = k.tol;
5446 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5447 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5452 std::vector<std::size_t> ists;
5456 " exceeds the number of nodes in the model (" +
5458 const std::size_t ist = sn.
nodes[k.node - 1].station;
5461 sn.
nodes[k.node - 1].name +
5462 "') is not a station, and a queue-length distribution is "
5463 "reported per station");
5464 ists.push_back(ist);
5466 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
5468 std::vector<std::size_t> rs;
5472 " exceeds the number of classes in the model");
5473 rs.push_back(k.jobclass);
5475 for (std::size_t c = 0; c < sn.
nclasses; ++c) rs.push_back(c + 1);
5480 std::vector<line::mva::MargResult<T> > curves;
5481 for (std::size_t a = 0; a < ists.size(); ++a)
5482 for (std::size_t b = 0; b < rs.size(); ++b)
5484 k.marg_states, opt.
method));
5486 if (g_json_output) {
5488 p[
"type"] =
"ProbMarg";
5491 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
5492 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
5495 e[
"station"] = ists[a] - 1;
5496 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5497 e[
"jobclass"] = rs[b] - 1;
5498 e[
"JobClass"] = sn.
classes[rs[b] - 1].name;
5500 for (std::size_t n = 0; n < m.
P.size(); ++n)
5501 jobs.push_back(k.marg_states.empty() ?
static_cast<long>(n)
5502 : k.marg_states[n]);
5504 e[
"P"] = vector_json(m.
P);
5505 e[
"logP"] = vector_json(m.
logP);
5508 p[
"marginal"] = arr;
5515 std::printf(
"%-16s %-14s %-8s %16s\n",
"Station",
"JobClass",
"Jobs",
"ProbMarg");
5516 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
5517 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
5519 for (std::size_t n = 0; n < m.
P.size(); ++n)
5520 std::printf(
"%-16s %-14s %-8ld %16.10g\n",
5521 sn.
stations[ists[a] - 1].name.c_str(),
5522 sn.
classes[rs[b] - 1].name.c_str(),
5523 k.marg_states.empty() ?
static_cast<long>(n) : k.marg_states[n],
5544int solve_model_nc_busyp(
const std::string& file,
const Knobs& k) {
5545 if (k.busy_subnet.empty())
5547 "-a busyperiod needs --busyperiod-subnet: the busy period is defined for a named "
5548 "subnetwork of stations, and no default can choose one");
5551 std::vector<std::size_t> subnet;
5552 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t) {
5555 std::to_string(k.busy_subnet[t]) +
", beyond the model's " +
5557 subnet.push_back(k.busy_subnet[t] - 1);
5559 std::vector<std::size_t> orders = k.busy_orders;
5560 if (orders.empty()) orders.push_back(1);
5563 if (g_json_output) {
5565 p[
"type"] =
"BusyPeriod";
5568 for (std::size_t t = 0; t < subnet.size(); ++t) sj.push_back(subnet[t]);
5570 for (std::size_t t = 0; t < orders.size(); ++t) oj.push_back(orders[t]);
5572 for (std::size_t t = 0; t < b.size(); ++t) bj.push_back(b[t]);
5576 emit_analysis<T>(
"busyperiod", p,
"daduna");
5580 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t)
5581 std::printf(
"%s%zu", t ?
"," :
"", k.busy_subnet[t]);
5583 for (std::size_t t = 0; t < orders.size(); ++t)
5584 std::printf(
" order %zu %.10g\n", orders[t], b[t]);
5607int solve_model_normconst(
const std::string& file,
const Knobs& k,
const std::string& solver) {
5612 if (solver ==
"nc") {
5614 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5615 if (k.tol >= 0.0) opt.
tol = k.tol;
5616 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5617 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5620 : std::numeric_limits<double>::quiet_NaN();
5624 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5625 if (k.tol >= 0.0) opt.
tol = k.tol;
5626 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5627 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5631 if (g_json_output) {
5633 p[
"type"] =
"NormConst";
5636 p[
"logNormConstAggr"] = lG;
5637 emit_analysis<T>(
"normconst", p, method);
5640 std::printf(
"Solver%s arith=%s method=%s lognormconst=%.10g\n",
5650 for (std::size_t i = 0; i < sn.
nstations; ++i)
5651 for (std::size_t r = 0; r < sn.
nclasses; ++r)
5667int solve_model_nc_prob(
const std::string& file,
const Knobs& k) {
5670 "the -s nc -a prob analysis exponentiates a difference of log normalizing constants "
5671 "and needs transcendental arithmetic; rerun with --arith double or --arith real");
5675 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5676 if (k.tol >= 0.0) opt.
tol = k.tol;
5677 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5678 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5695 if (!k.state.empty()) {
5698 "--state is the state of ONE node and needs --node to say which");
5699 const std::size_t ist =
5700 k.node <= sn.
nodes.size() ? sn.
nodes[k.node - 1].station : 0;
5703 " is not a station, so it has no queue-length state");
5706 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5711 std::vector<T> row(k.state.size());
5712 for (std::size_t i = 0; i < k.state.size(); ++i)
5716 for (std::size_t c = 0; c < sn.
nclasses; ++c)
5751 std::numeric_limits<double>::quiet_NaN());
5759 std::numeric_limits<double>::quiet_NaN());
5769 if (g_json_output) {
5771 p[
"type"] =
"ProbAggr";
5775 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
5779 line::reg::Json pa = line::reg::Json::array(), pm = line::reg::Json::array();
5780 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5787 emit_analysis<T>(
"prob", p, am);
5794 std::printf(
"ProbSys, Prob and ProbAggr need a load-dependent normalizing constant, "
5795 "which method '%s' does not compute\n",
5800 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
5801 for (std::size_t i = 0; i < sn.
nstations; ++i)
5802 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
5825int solve_model_nc_sysmarg(
const std::string& file,
const Knobs& k) {
5828 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5829 if (k.tol >= 0.0) opt.
tol = k.tol;
5833 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
5834 const double pop = sn.
classes[r].population;
5835 if (!std::isfinite(pop))
5837 "getProbSysMarg requires a closed model: the joint law of the total queue lengths "
5838 "is not defined when a class has an infinite population");
5842 static_cast<int>(sn.
nstations),
static_cast<int>(std::llround(Ntot)));
5844 std::vector<double>
P(states.size(), 0.0);
5845 for (std::size_t j = 0; j < states.size(); ++j)
5849 if (g_json_output) {
5851 p[
"type"] =
"ProbSysMarg";
5853 p[
"engine"] = k.method_perm;
5854 line::reg::Json st = line::reg::Json::array(), arr = line::reg::Json::array();
5855 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
5856 for (std::size_t j = 0; j < states.size(); ++j) {
5859 for (std::size_t i = 0; i < sn.
nstations; ++i) n.push_back(states[j][i]);
5866 emit_analysis<T>(
"sysmarg", p, opt.
method);
5870 opt.
method.c_str(), k.method_perm.c_str());
5871 for (std::size_t i = 0; i < sn.
nstations; ++i)
5872 std::printf(
"%10s", sn.
stations[i].name.c_str());
5873 std::printf(
" %14s\n",
"ProbSysMarg");
5875 for (std::size_t j = 0; j < states.size(); ++j) {
5876 for (std::size_t i = 0; i < sn.
nstations; ++i) std::printf(
"%10d", states[j][i]);
5877 std::printf(
" %14.10g\n", P[j]);
5880 std::printf(
"%*s %14.10g\n",
static_cast<int>(10 * sn.
nstations),
"sum", total);
5902int solve_model_nc_marg(
const std::string& file,
const Knobs& k) {
5905 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5906 if (k.tol >= 0.0) opt.
tol = k.tol;
5907 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5908 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5911 std::vector<std::size_t> ists;
5915 " exceeds the number of nodes in the model (" +
5917 const std::size_t ist = sn.
nodes[k.node - 1].station;
5920 sn.
nodes[k.node - 1].name +
5921 "') is not a station, and a queue-length distribution is "
5922 "reported per station");
5923 ists.push_back(ist);
5925 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
5930 std::vector<line::nc::NcQueueLengthDist<T> > curves;
5931 for (std::size_t a = 0; a < ists.size(); ++a)
5934 if (g_json_output) {
5936 p[
"type"] =
"ProbMargAggr";
5939 for (std::size_t a = 0; a < ists.size(); ++a) {
5941 e[
"station"] = ists[a] - 1;
5942 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5943 e[
"P"] = vector_json<T>(curves[a].P);
5944 e[
"logP"] = vector_json<T>(curves[a].logP);
5948 emit_analysis<T>(
"marg", p, opt.
method);
5953 for (std::size_t a = 0; a < ists.size(); ++a) {
5954 std::printf(
"%-16s %-8s %14s %14s\n",
"Station",
"n",
"P",
"logP");
5955 for (std::size_t n = 0; n < curves[a].P.size(); ++n)
5956 std::printf(
"%-16s %-8zu %14.10g %14.10g\n", sn.
stations[ists[a] - 1].name.c_str(), n,
5979int solve_model_nc_cdf(
const std::string& file,
const Knobs& k) {
5982 "the -s nc -a cdf analysis evaluates the sojourn law on a logarithmic time grid and "
5983 "inverts a generating function; it needs transcendental arithmetic, so rerun with "
5984 "--arith double or --arith real");
5988 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5989 if (k.tol >= 0.0) opt.
tol = k.tol;
5990 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5991 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5992 if (!k.cdf_algorithm.empty()) opt.
cdf_algorithm = k.cdf_algorithm;
5995 if (!r.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", r.
warning.c_str());
5997 if (g_json_output) {
5999 p[
"type"] =
"CdfRespT";
6006 for (std::size_t i = 0; i < r.
RD.size(); ++i)
6007 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
6011 if (r.
RD[i][c].empty())
continue;
6013 e[
"Station"] = sn.
stations[i].name;
6014 e[
"JobClass"] = sn.
classes[c].name;
6017 line::reg::Json tt = line::reg::Json::array(), ff = line::reg::Json::array();
6018 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j) {
6027 p[
"tset"] = vector_json(r.
tset);
6032 emit_analysis<T>(
"cdf", p, std::string());
6037 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
6038 for (std::size_t i = 0; i < r.
RD.size(); ++i)
6039 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
6040 if (r.
RD[i][c].empty())
continue;
6041 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j)
6042 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
6063double sens_sanitize_signed(
double x) {
6091int solve_model_nc_sens(
const std::string& file,
const Knobs& k) {
6094 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
6095 if (k.tol >= 0.0) opt.
tol = k.tol;
6096 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
6097 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
6101 if (!k.sens_method.empty()) so.
method = k.sens_method;
6102 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
6103 if (k.sens_step > 0.0) so.
step = k.sens_step;
6109 sn, so,
true, [&sn, &opt]() {
6123 if (g_json_output) {
6125 p[
"type"] =
"SensitivityTable";
6127 p[
"branch"] = tbl.
method;
6145 emit_analysis<T>(
"sens", p, tbl.
method ==
"fd" ? opt.
method : std::string());
6152 std::printf(
"%-16s %-14s %14s %14s %14s %14s\n",
"Station",
"JobClass",
"dTput_dRate",
6153 "dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
6155 std::printf(
"%-16s %-14s %14.6g %14.6g %14.6g %14.6g\n", r.
station.c_str(),
6183 if (k.samples) o.
samples = k.samples;
6184 if (k.seed) o.
seed =
static_cast<long>(k.seed);
6185 if (!k.method.empty()) o.
method = k.method;
6186 if (!k.ldes_tranfilter.empty()) o.
tranfilter = k.ldes_tranfilter;
6187 if (k.ldes_warmupfrac >= 0.0) o.
warmupfrac = k.ldes_warmupfrac;
6188 if (!k.ldes_cimethod.empty()) o.
cimethod = k.ldes_cimethod;
6189 if (k.ldes_cnvgon) o.
cnvgon =
true;
6190 if (k.ldes_cnvgtol > 0.0) o.
cnvgtol = k.ldes_cnvgtol;
6191 if (k.ldes_slotted) o.
slotted =
true;
6192 if (k.ldes_slotlength > 0.0) {
6196 if (k.ldes_replications > 0) o.
replications = k.ldes_replications;
6197 if (k.ldes_numthreads > 0) o.
numthreads = k.ldes_numthreads;
6198 if (k.ldes_maxtime > 0.0) o.
timeout = k.ldes_maxtime;
6199 if (!k.ldes_initsol.empty()) o.
init_sol = k.ldes_initsol;
6200 if (!k.ldes_rest_url.empty()) o.
rest_url = k.ldes_rest_url;
6213inline std::string ldes_document(
const std::string& file) {
6214 return file.empty() ? stdin_model_text() :
line::ldes::detail::read_file(file);
6219 return i < M.
rows() && j < M.
cols() ? M(i, j) : 0.0;
6233 const std::vector<std::string>& extra) {
6237 "SolverLDES exceeded its wall-clock budget (--ldes-maxtime) and was terminated before "
6238 "it wrote a result; raise the budget or lower --samples");
6241 "SolverLDES: the engine reported no station names, so its metrics cannot be labelled; "
6242 "the run produced no result document");
6256 std::printf(
"SolverLDES arith=double method=%s type=%s engine=%s samples=%zu seed=%ld "
6257 "time=%.6g events=%lld stopping=%s\n",
6287int solve_model_ldes_avg(
const std::string& file,
const Knobs& k) {
6290 if (!g_json_output) ldes_banner(r, o);
6297 if (!r.
QNCI.
empty()) ci[
"QNCI"] = matrix_json<double>(r.
QNCI);
6298 if (!r.
UNCI.
empty()) ci[
"UNCI"] = matrix_json<double>(r.
UNCI);
6299 if (!r.
RNCI.
empty()) ci[
"RNCI"] = matrix_json<double>(r.
RNCI);
6300 if (!r.
TNCI.
empty()) ci[
"TNCI"] = matrix_json<double>(r.
TNCI);
6301 if (!r.
ANCI.
empty()) ci[
"ANCI"] = matrix_json<double>(r.
ANCI);
6302 if (!r.
WNCI.
empty()) ci[
"WNCI"] = matrix_json<double>(r.
WNCI);
6303 if (!ci.empty()) extra[
"CI"] = ci;
6307 f[
"QNfcr"] = matrix_json<double>(r.
QNfcr);
6308 f[
"RNfcr"] = matrix_json<double>(r.
RNfcr);
6309 f[
"TNfcr"] = matrix_json<double>(r.
TNfcr);
6310 f[
"WNfcr"] = matrix_json<double>(r.
WNfcr);
6320 if (!fjb.is_null()) extra[
"FJ"] = fjb;
6324 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
6328 if (!it->second.hit.empty()) c[
"hit"] = matrix_json<double>(it->second.hit);
6329 if (!it->second.delayed.empty()) c[
"delayed"] = matrix_json<double>(it->second.delayed);
6330 if (!it->second.miss.empty()) c[
"miss"] = matrix_json<double>(it->second.miss);
6331 if (!it->second.latency.empty()) c[
"latency"] = matrix_json<double>(it->second.latency);
6332 if (!it->second.hitList.empty()) c[
"hitList"] = matrix_json<double>(it->second.hitList);
6333 if (!it->second.itemProb.empty())
6334 c[
"itemProb"] = matrix_json<double>(it->second.itemProb);
6335 if (!it->second.listCost.empty())
6336 c[
"listCost"] = matrix_json<double>(it->second.listCost);
6339 extra[
"cacheMetrics"] = cm;
6360 for (std::size_t j = 0; j < sn.
nstations; ++j)
6362 std::vector<std::size_t> cl_of(r.
class_names.size(), 0);
6363 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6364 for (std::size_t k = 0; k < sn.
nclasses; ++k)
6368 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6369 if (st_of[i] && cl_of[c]) RNs(st_of[i] - 1, cl_of[c] - 1) = ldes_at(r.
RN, i, c);
6372 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6373 WNd(i, c) = (st_of[i] && cl_of[c]) ? WNs(st_of[i] - 1, cl_of[c] - 1)
6374 : ldes_at(r.
WN, i, c);
6378 [&](std::size_t i, std::size_t c) {
6380 v.q = ldes_at(r.QN, i, c);
6381 v.u = ldes_at(r.UN, i, c);
6382 v.r = ldes_at(r.RN, i, c);
6384 v.a = ldes_at(r.AN, i, c);
6385 v.t = ldes_at(r.TN, i, c);
6388 extra, ldes_envelope(r, o));
6391 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s\n",
"Region",
"JobClass",
"QLen",
"RespT",
6392 "Tput",
"Weight",
"MemOcc");
6393 for (std::size_t i = 0; i < r.
QNfcr.
rows(); ++i)
6394 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6395 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
6396 (
"Region" + std::to_string(i + 1)).c_str(), r.
class_names[c].c_str(),
6397 ldes_at(r.
QNfcr, i, c), ldes_at(r.
RNfcr, i, c),
6402 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Cache",
"JobClass",
"Hit",
"Delayed",
6404 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
6407 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6408 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", it->first.c_str(),
6409 r.
class_names[c].c_str(), ldes_at(it->second.hit, 0, c),
6410 ldes_at(it->second.delayed, 0, c), ldes_at(it->second.miss, 0, c),
6411 ldes_at(it->second.latency, 0, c));
6430int solve_model_ldes_tran(
const std::string& file,
const Knobs& k) {
6435 std::vector<std::string> extra;
6436 extra.push_back(
"--trajectory");
6438 if (r.
t.empty() || r.
QNt.empty())
6440 "SolverLDES -a tran produced no trajectory: the engine ran but recorded no bucket over "
6441 "[" + line::ldes::detail::shortest(k.t0) +
"," +
6442 line::ldes::detail::shortest(k.t1) +
"]");
6444 if (g_json_output) {
6446 p[
"type"] =
"TranAvgTable";
6451 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
6452 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
6455 if (r.
QNt[i][c].empty())
continue;
6458 :
"Station" + std::to_string(i);
6463 line::reg::Json tt = line::reg::Json::array(), q = line::reg::Json::array(),
6464 u = line::reg::Json::array(), x = line::reg::Json::array();
6465 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
6466 tt.push_back(r.
QNt[i][c](j, 1));
6467 q.push_back(r.
QNt[i][c](j, 0));
6469 if (i < r.
UNt.size() && c < r.
UNt[i].size())
6470 for (std::size_t j = 0; j < r.
UNt[i][c].rows(); ++j)
6471 u.push_back(r.
UNt[i][c](j, 0));
6472 if (i < r.
TNt.size() && c < r.
TNt[i].size())
6473 for (std::size_t j = 0; j < r.
TNt[i][c].rows(); ++j)
6474 x.push_back(r.
TNt[i][c](j, 0));
6479 curves.push_back(e);
6481 p[
"curves"] = curves;
6482 p[
"tset"] = vector_json(r.
t);
6487 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6488 p[it.key()] = it.value();
6489 emit_analysis<double>(
"tran", p, r.
method);
6493 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
6495 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
6496 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
6497 if (r.
QNt[i][c].empty())
continue;
6498 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
6499 const bool hu = i < r.
UNt.size() && c < r.
UNt[i].size() &&
6500 j < r.
UNt[i][c].rows();
6501 const bool hx = i < r.
TNt.size() && c < r.
TNt[i].size() &&
6502 j < r.
TNt[i][c].rows();
6503 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
6506 r.
QNt[i][c](j, 1), r.
QNt[i][c](j, 0),
6507 hu ? r.
UNt[i][c](j, 0) : 0.0, hx ? r.
TNt[i][c](j, 0) : 0.0);
6526int solve_model_ldes_cdf(
const std::string& file,
const Knobs& k,
const char* key,
6529 std::vector<std::string> extra;
6530 extra.push_back(
"--respt-samples");
6534 "SolverLDES -a cdf needs the per-job response times the engine records under "
6535 "--respt-samples and the run returned none; raise --samples so completions are "
6540 std::vector<std::vector<std::vector<double>>> tt(r.
respTimeSamples.size()), ff(
6547 if (x.empty())
continue;
6548 std::sort(x.begin(), x.end());
6549 const double n =
static_cast<double>(x.size());
6550 for (std::size_t j = 0; j < x.size(); ++j) {
6551 if (j + 1 < x.size() && x[j + 1] == x[j])
continue;
6552 tt[i][c].push_back(x[j]);
6553 ff[i][c].push_back(
static_cast<double>(j + 1) / n);
6558 if (g_json_output) {
6562 p[
"algorithm"] =
"empirical";
6564 for (std::size_t i = 0; i < tt.size(); ++i)
6565 for (std::size_t c = 0; c < tt[i].size(); ++c) {
6566 if (tt[i][c].empty())
continue;
6569 :
"Station" + std::to_string(i);
6574 e[
"t"] = vector_json(tt[i][c]);
6575 e[
"F"] = vector_json(ff[i][c]);
6580 emit_analysis<double>(key, p, std::string());
6584 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
6585 for (std::size_t i = 0; i < tt.size(); ++i)
6586 for (std::size_t c = 0; c < tt[i].size(); ++c)
6587 for (std::size_t j = 0; j < tt[i][c].size(); ++j)
6588 std::printf(
"%-16s %-14s %14.8g %14.10g\n",
6591 tt[i][c][j], ff[i][c][j]);
6608int solve_model_ldes_sample(
const std::string& file,
const Knobs& k) {
6613 std::vector<std::string> extra;
6614 extra.push_back(
"--trajectory");
6616 if (r.
t.empty() || r.
QNt.empty())
6618 "SolverLDES -a sample produced no trajectory over [0," +
6619 line::ldes::detail::shortest(o.
t1) +
"]");
6621 const std::size_t M = r.
QNt.size(), K = r.
class_names.size(), n = r.
t.size();
6622 if (g_json_output) {
6624 p[
"type"] =
"SamplePath";
6626 p[
"scope"] =
"(system)";
6628 p[
"t"] = vector_json(r.
t);
6630 for (std::size_t j = 0; j < n; ++j) {
6632 for (std::size_t i = 0; i < M; ++i)
6633 for (std::size_t c = 0; c < K; ++c)
6634 row.push_back(c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
6641 for (std::size_t i = 0; i < M; ++i)
6642 for (std::size_t c = 0; c < K; ++c)
6645 p[
"columns"] = cols;
6650 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6651 p[it.key()] = it.value();
6652 emit_analysis<double>(
"sample", p, r.
method);
6656 std::printf(
"%14s %s\n",
"Time",
"SysState (station-major, per class)");
6657 for (std::size_t j = 0; j < n; ++j) {
6658 std::printf(
"%14.8g ", r.
t[j]);
6659 for (std::size_t i = 0; i < M; ++i)
6660 for (std::size_t c = 0; c < K; ++c)
6661 std::printf(
" %g", c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
6683int solve_model_ldes_reward(
const std::string& file,
const Knobs& k) {
6688 "-s ldes -a reward needs a reward declared on the model (set_reward(name, fn), the "
6689 "`rewards` block of model.json); there is nothing to average");
6692 std::vector<std::string> extra;
6693 extra.push_back(
"--export-histogram");
6697 "SolverLDES -a reward needs the joint-state residence-time histogram the engine "
6698 "exports under --export-histogram and the run returned none");
6705 "SolverLDES -a reward: the state histogram carries no residence time, so no state "
6706 "distribution can be formed from it");
6709 std::vector<double> E(sn.
reward.size(), 0.0);
6710 std::vector<std::string> names(sn.
reward.size());
6711 for (std::size_t l = 0; l < sn.
reward.size(); ++l) {
6712 names[l] = sn.
reward[l].name;
6713 for (std::size_t s = 0; s < ns; ++s) {
6714 std::vector<double> row(w);
6719 E[l] += (t / total) * sn.
reward[l].fn(row);
6723 if (g_json_output) {
6725 p[
"type"] =
"AvgReward";
6727 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
6729 p[
"E"] = vector_json(E);
6735 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6736 p[it.key()] = it.value();
6739 emit_analysis<double>(
"reward", p, std::string());
6743 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
6744 for (std::size_t l = 0; l < E.size(); ++l)
6745 std::printf(
"%-28s %16.10g\n", names[l].c_str(), E[l]);
6768int solve_model_ldes_prob(
const std::string& file,
const Knobs& k) {
6774 if (!k.state.empty()) {
6775 std::size_t ist = 0;
6776 if (k.node && k.node <= sn.
nodes.size()) ist = sn.
nodes[k.node - 1].station;
6779 " is not a station; the LDES state histogram records station "
6780 "queue lengths only");
6781 if (k.state.size() != R)
6782 throw line::InputError(
"-s ldes -a prob takes --state as one job count per class (" +
6783 std::to_string(R) +
"), got " + std::to_string(k.state.size()));
6784 for (std::size_t r = 0; r < R; ++r) nir(ist - 1, r) =
static_cast<double>(k.state[r]);
6788 std::vector<std::string> extra;
6789 extra.push_back(
"--export-histogram");
6795 "SolverLDES -a prob needs the joint-state residence-time histogram the engine exports "
6796 "under --export-histogram and the run returned none");
6797 if (space.
cols() < M * R)
6799 std::to_string(space.
cols()) +
" columns, fewer than the " +
6800 std::to_string(M * R) +
" of " + std::to_string(M) +
6801 " stations by " + std::to_string(R) +
" classes");
6803 std::vector<double> dwell(space.
rows(), 0.0);
6805 for (std::size_t s = 0; s < space.
rows() && s < tm.
rows(); ++s)
6806 for (std::size_t c = 0; c < tm.
cols(); ++c) dwell[s] += tm(s, c);
6807 for (
double d : dwell) total += d;
6810 "SolverLDES -a prob: the state histogram carries no residence time, so no state "
6811 "probability can be formed from it");
6814 auto matches = [&](std::size_t s, std::size_t i) {
6815 for (std::size_t r = 0; r < R; ++r)
6816 if (!(std::abs(space(s, i * R + r) - nir(i, r)) < 1e-9))
return false;
6819 std::vector<double> prob(M, 0.0);
6820 std::vector<bool> seen(M,
false);
6822 bool sys_seen =
false;
6823 for (std::size_t s = 0; s < space.
rows(); ++s) {
6825 for (std::size_t i = 0; i < M; ++i) {
6827 if (matches(s, i)) {
6828 prob[i] += dwell[s];
6839 for (
double& p : prob) p /= total;
6842 if (g_json_output) {
6844 p[
"type"] =
"ProbAggr";
6846 p[
"ProbSys"] = psys;
6847 p[
"ProbSysAggr"] = psys;
6848 p[
"SysStateSeen"] = sys_seen;
6849 p[
"states"] = space.
rows();
6850 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
6851 sv = line::reg::Json::array();
6852 for (std::size_t i = 0; i < M; ++i) {
6854 pa.push_back(prob[i]);
6855 sv.push_back(
static_cast<bool>(seen[i]));
6860 p[
"StateSeen"] = sv;
6861 emit_analysis<double>(
"prob", p, res.
method);
6864 ldes_banner(res, o);
6865 std::printf(
"ProbSys = ProbSysAggr %.10g%s\n", psys, sys_seen ?
"" :
" (state never observed)");
6866 std::printf(
"%-16s %14s\n",
"Station",
"Prob=ProbAggr");
6867 for (std::size_t i = 0; i < M; ++i)
6868 std::printf(
"%-16s %14.10g%s\n", sn.
stations[i].name.c_str(), prob[i],
6869 seen[i] ?
"" :
" (state never observed)");
6879int solve_model_ldes(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
6880 if (analysis ==
"avg")
return solve_model_ldes_avg(file, k);
6881 if (analysis ==
"tran")
return solve_model_ldes_tran(file, k);
6889 if (analysis ==
"cdf")
return solve_model_ldes_cdf(file, k,
"cdf",
"CdfRespT");
6890 if (analysis ==
"cdfpasst")
return solve_model_ldes_cdf(file, k,
"cdfpasst",
"CdfPassT");
6891 if (analysis ==
"trancdf")
return solve_model_ldes_cdf(file, k,
"trancdf",
"TranCdfRespT");
6892 if (analysis ==
"trancdfpasst")
6893 return solve_model_ldes_cdf(file, k,
"trancdfpasst",
"TranCdfPassT");
6894 if (analysis ==
"sample")
return solve_model_ldes_sample(file, k);
6895 if (analysis ==
"reward")
return solve_model_ldes_reward(file, k);
6896 if (analysis ==
"prob")
return solve_model_ldes_prob(file, k);
6898 "SolverLDES ports -a avg (getAvg) and its four views -a node, -a sys, -a chain and "
6899 "-a nodechain, -a tran (getTranAvg), -a cdf / cdf-passt / "
6900 "tran-cdf-respt / tran-cdf-passt (the empirical passage law, one measurement under the "
6901 "four names the reference gives it), -a sample (sampleSys), -a reward "
6902 "(getAvgReward) and -a prob (getProb, getProbAggr, getProbSys, getProbSysAggr); got '" +
6906std::string auto_getter_of_analysis(
const std::string& analysis) {
6907 if (analysis ==
"prob")
return "getProbSysAggr";
6908 if (analysis ==
"marg")
return "getProbMarg";
6909 if (analysis ==
"sysmarg")
return "getProbSysMarg";
6910 if (analysis ==
"normconst")
return "getProbNormConstAggr";
6911 if (analysis ==
"tranprob")
return "getTranProbSysAggr";
6912 if (analysis ==
"sample")
return "sampleSys";
6913 if (analysis ==
"cdf")
return "getCdfRespT";
6914 if (analysis ==
"gen")
return "getInfGen";
6915 if (analysis ==
"states")
return "getStateSpace";
6916 if (analysis ==
"reward")
return "getAvgReward";
6917 if (analysis ==
"sens")
return "getSensitivityTable";
6918 if (analysis ==
"tran")
return "getTranAvg";
6919 if (analysis ==
"internals")
return "getMAMResult";
6925 if (analysis ==
"node")
return "getAvgNodeTable";
6926 return "getAvgTable";
6934 std::vector<std::string> order;
6938 std::vector<std::string> stage;
6942std::string auto_cli_token_of_family(
const std::string& fam) {
6943 if (fam ==
"mva" || fam ==
"nc" || fam ==
"ctmc" || fam ==
"mam" || fam ==
"ag" ||
6944 fam ==
"ssa" || fam ==
"ba" || fam ==
"uq" || fam ==
"env")
6946 if (fam ==
"fld")
return "fluid";
6947 if (fam ==
"ldes") {
6953 "--method ldes names the discrete-event engine, and no engine was found beside "
6954 "this binary (common/ldes or common/ldes.jar, or $LINE_LDES_DIR); -s ssa is the "
6955 "simulator this port builds in process");
6969 if (fam ==
"lqns")
return "lqns";
6972 "--method ln names the layered solver, which takes a LayeredNetwork: pass the model "
6973 "as -i lqnx -s ln rather than as a Network");
6974 throw line::InputError(
"SolverAUTO: no engine stands behind method family '" + fam +
"'");
6985bool model_is_environment(
const std::string& file) {
6987 line::io::detail::json root;
6989 std::istringstream
in(stdin_model_text());
6992 std::ifstream
in(file.c_str());
6993 if (!in)
return false;
6999 if (!root.is_object())
return false;
7000 const line::io::detail::json& model = root.contains(
"model") ? root.at(
"model") : root;
7001 return model.is_object() && model.value(
"type", std::string()) ==
"Environment";
7027AutoPlan choose_auto_plan(
const std::string& file,
const std::string& analysis,
7028 const std::string& method_token) {
7035 if (model_is_environment(file))
throw;
7040 plan.order.push_back(auto_cli_token_of_family(tok.
family));
7045 const std::string getter = auto_getter_of_analysis(analysis);
7046 if (model_is_environment(file)) {
7051 for (std::size_t i = 0; i < ec.
retry.size(); ++i) {
7052 plan.order.push_back(
"env");
7055 for (std::size_t i = 0; i < ec.
skipped.size(); ++i)
7056 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
7058 if (!ec.
skipped.empty()) plan.note +=
", which this port does not build)";
7066 const std::vector<line::autosolver::AutoSolver> proposed =
7068 for (std::size_t i = 0; i < proposed.size(); ++i)
7070 for (std::size_t i = 0; i < c.
skipped.size(); ++i)
7071 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
7073 if (!c.
skipped.empty()) plan.note +=
", which this port does not build)";
7077AutoPlan choose_auto_plan_dispatch(
const std::string& arith,
const std::string& file,
7078 const std::string& analysis,
const std::string& method_token) {
7079 if (arith ==
"exact")
return choose_auto_plan<line::Rational>(file, analysis, method_token);
7080 if (arith ==
"real:16")
return choose_auto_plan<line::Real<16> >(file, analysis, method_token);
7081 if (arith ==
"real" || arith ==
"real:32")
7082 return choose_auto_plan<line::Real<32> >(file, analysis, method_token);
7083 if (arith ==
"real:64")
return choose_auto_plan<line::Real<64> >(file, analysis, method_token);
7084 if (arith ==
"real:128")
7085 return choose_auto_plan<line::Real<128> >(file, analysis, method_token);
7086 if (arith ==
"real:256")
7087 return choose_auto_plan<line::Real<256> >(file, analysis, method_token);
7088 return choose_auto_plan<double>(file, analysis, method_token);
7102double ln_sanitize(
double x) {
7103 const double r = std::round(x * 10.0);
7110 switch (l.
type[i]) {
7114 default:
return "Activity";
7130 using namespace line;
7131 const std::vector<qn::Layer<T> >& ens = solver.
layers();
7132 for (std::size_t k = 0; k < ens.size(); ++k) {
7134 std::printf(
"LAYER %zu %s nstations=%zu nclasses=%zu nchains=%zu\n", k + 1, L.
name.c_str(),
7136 for (std::size_t i = 0; i < L.
stations.size(); ++i)
7137 std::printf(
" STATION %zu %s sched=%s nservers=%g\n", i + 1, L.
stations[i].name.c_str(),
7139 for (std::size_t r = 0; r < L.
classes.size(); ++r)
7140 std::printf(
" CLASS %zu %s pop=%.17g refstat=%zu completes=%d\n", r + 1,
7143 for (std::size_t i = 0; i < L.
stations.size(); ++i)
7144 for (std::size_t r = 0; r < L.
classes.size(); ++r) {
7146 std::printf(
" RATE %s %s %.17g scv=%.17g\n", L.
stations[i].name.c_str(),
7150 for (
const auto& kv : L.
P) {
7152 for (std::size_t i = 0; i < B.
rows(); ++i)
7153 for (std::size_t j = 0; j < B.
cols(); ++j) {
7155 if (p == 0.0)
continue;
7156 std::printf(
" ROUTE %s->%s %s->%s %.17g\n",
7157 L.
classes[kv.first.first - 1].name.c_str(),
7158 L.
classes[kv.first.second - 1].name.c_str(),
7159 L.
nodes[i].name.c_str(), L.
nodes[j].name.c_str(), p);
7167 using namespace line;
7172 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
7173 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
7174 if (k.no_interlocking)
opt.interlocking =
false;
7175 if (!k.interlock_method.empty())
opt.interlock_method = k.interlock_method;
7176 if (k.interlock_maxpaths >= 0.0)
opt.interlock_maxpaths = k.interlock_maxpaths;
7177 if (!k.interlock_refpath_scope.empty())
opt.interlock_refpath_scope = k.interlock_refpath_scope;
7178 if (!k.layer_solver.empty())
opt.layer_solver = k.layer_solver;
7179 if (!k.method.empty())
opt.method = k.method;
7183 opt.layer_ssa.samples = k.samples;
7184 opt.layer_ldes.samples =
static_cast<std::size_t
>(k.samples);
7185 opt.layer_jmt.samples =
static_cast<double>(k.samples);
7188 opt.layer_ssa.seed = k.seed;
7189 opt.layer_ldes.seed =
static_cast<long>(k.seed);
7190 opt.layer_jmt.seed =
static_cast<long>(k.seed);
7192 if (k.t1 >= 0.0)
opt.timespan_end = k.t1;
7193 if (k.tran_points)
opt.tran_points = k.tran_points;
7194 if (!k.ln_transient.empty())
opt.ln_transient = k.ln_transient;
7195 if (!k.ln_transient_channels.empty())
opt.ln_transient_channels = k.ln_transient_channels;
7196 for (
const auto& kv : k.lld)
opt.lld[kv.first] = kv.second;
7197 if (k.nlp_alpha >= 0.0)
opt.nlp_alpha = k.nlp_alpha;
7198 if (k.nlp_warm >= 0)
opt.nlp_warm = int(k.nlp_warm);
7199 if (k.nlp_maxiter >= 0)
opt.nlp_maxiter = int(k.nlp_maxiter);
7200 if (!k.nlp_fork.empty())
opt.nlp_fork = k.nlp_fork;
7201 if (!k.warmstart.empty())
opt.warmstart = k.warmstart;
7202 if (k.mol_iter_max >= 0)
opt.mol_iter_max = std::size_t(k.mol_iter_max);
7203 if (k.mol_iter_tol >= 0.0)
opt.mol_iter_tol = k.mol_iter_tol;
7204 if (k.mol_relax_factor >= 0.0)
opt.mol_relax_factor = k.mol_relax_factor;
7209inline const char* ln_layer_engine_name(
const std::string& layer_solver) {
7210 if (layer_solver ==
"fluid")
return "Fluid";
7211 if (layer_solver ==
"nc")
return "NC";
7212 if (layer_solver ==
"ssa")
return "SSA";
7213 if (layer_solver ==
"mam")
return "MAM";
7214 if (layer_solver ==
"ctmc")
return "CTMC";
7215 if (layer_solver ==
"ldes")
return "LDES";
7216 if (layer_solver ==
"jmt")
return "JMT";
7228int run_ln_tran(
const std::string& file,
const std::string& output,
const Knobs& k) {
7229 using namespace line;
7234 const std::vector<qn::Layer<T>>& layers = solver.
layers();
7236 if (output ==
"json") {
7240 j[
"mode"] =
tr.mode;
7241 j[
"iterations"] =
tr.iterations;
7242 j[
"gap"] = ln_sanitize(
tr.gap);
7244 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
7246 le[
"layer"] = layers[e].name;
7248 for (
double t :
tr.layers[e].t) tt.push_back(ln_sanitize(t));
7251 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
7252 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
7254 s[
"station"] = layers[e].stations[i].name;
7255 s[
"class"] = layers[e].classes[r].name;
7256 auto arr = [&](
const std::vector<double>& v) {
7258 for (
double x : v) a.push_back(ln_sanitize(x));
7261 s[
"QLen"] = arr(
tr.layers[e].QN[i][r]);
7262 s[
"Util"] = arr(
tr.layers[e].UN[i][r]);
7263 s[
"Tput"] = arr(
tr.layers[e].TN[i][r]);
7264 series.push_back(s);
7266 le[
"series"] = series;
7270 emit_document(dump_document(j, 1));
7274 std::printf(
"SolverLN(Solver%s) getTranAvg arith=%s mode=%s layers=%zu iterations=%ld gap=%.3e\n",
7276 tr.layers.size(),
tr.iterations,
tr.gap);
7277 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
7278 const std::vector<double>& t =
tr.layers[e].t;
7279 if (t.empty())
continue;
7280 std::printf(
"\nLayer %s (%zu points on [%.6g, %.6g])\n", layers[e].
name.c_str(), t.size(),
7281 t.front(), t.back());
7282 std::printf(
"%-30s %-24s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen(0)",
7283 "QLen(end)",
"Util(end)",
"Tput(end)");
7284 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
7285 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
7286 const std::vector<double>& q =
tr.layers[e].QN[i][r];
7287 if (q.empty())
continue;
7288 std::printf(
"%-30s %-24s %12.6g %12.6g %12.6g %12.6g\n",
7289 layers[e].stations[i].
name.c_str(), layers[e].classes[r].name.c_str(),
7290 ln_sanitize(q.front()), ln_sanitize(q.back()),
7291 ln_sanitize(
tr.layers[e].UN[i][r].back()),
7292 ln_sanitize(
tr.layers[e].TN[i][r].back()));
7300int run_ln_sens(
const std::string& file,
const std::string& output,
const Knobs& k) {
7301 using namespace line;
7306 if (!k.sens_method.empty()) so.
method = k.sens_method;
7307 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
7308 if (k.sens_step > 0.0) so.
step = k.sens_step;
7311 if (output ==
"json") {
7315 j[
"method"] = tbl.
method;
7317 for (
const auto& r : tbl.
rows) {
7319 o[
"Layer"] = r.layer;
7320 o[
"Station"] = r.station;
7321 o[
"JobClass"] = r.jobclass;
7329 emit_document(dump_document(j, 1));
7333 std::printf(
"SolverLN(Solver%s) getSensitivityTable arith=%s method=%s rows=%zu\n",
7336 std::printf(
"%-28s %-28s %-20s %14s %14s %14s %14s\n",
"Layer",
"Station",
"JobClass",
7337 "dTput_dRate",
"dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
7338 for (
const auto& r : tbl.
rows)
7339 std::printf(
"%-28s %-28s %-20s %14.6g %14.6g %14.6g %14.6g\n", r.layer.c_str(),
7340 r.station.c_str(), r.jobclass.c_str(),
7350int run_ln_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
7351 using namespace line;
7357 opt.method =
"moment3";
7362 if (output ==
"json") {
7367 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
7369 o[
"entry"] = names.names[model.
eshift + e];
7370 reg::Json tt = reg::Json::array(), ff = reg::Json::array();
7371 for (std::size_t p = 0; p < cdf[e].t.size(); ++p) {
7372 tt.push_back(ln_sanitize(cdf[e].t[p]));
7373 ff.push_back(ln_sanitize(cdf[e].cdf[p]));
7379 j[
"entries"] = rows;
7380 emit_document(dump_document(j, 1));
7384 std::printf(
"SolverLN(Solver%s) getCdfRespT arith=%s method=moment3 entries=%zu\n",
7386 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
7387 if (cdf[e].t.empty()) {
7388 std::printf(
"%-40s (no distribution: the entry has no fitted term)\n",
7389 names.names[model.
eshift + e].c_str());
7393 auto quantile = [&](
double p) {
7394 for (std::size_t i = 0; i < cdf[e].cdf.size(); ++i)
7395 if (cdf[e].cdf[i] >= p)
return cdf[e].t[i];
7396 return cdf[e].t.back();
7398 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g points=%zu\n",
7399 names.names[model.
eshift + e].c_str(), ln_sanitize(quantile(0.25)),
7400 ln_sanitize(quantile(0.50)), ln_sanitize(quantile(0.75)),
7401 ln_sanitize(quantile(0.95)), cdf[e].t.size());
7418int run_ln(
const std::string& file,
const std::string& output,
const Knobs& k) {
7419 using namespace line;
7423 const int repeat = k.repeat > 0 ? k.repeat : 1;
7425 double best = 1e300;
7427 std::size_t nlayers = 0;
7428 for (
int rep = 0; rep < repeat; ++rep) {
7429 const auto t0 = std::chrono::steady_clock::now();
7433 if (output ==
"layers") {
7434 ln_dump_layers(solver);
7437 const auto t1 = std::chrono::steady_clock::now();
7438 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
7440 if (!sol.
warning.empty()) std::fprintf(stderr,
"Warning: %s\n", sol.
warning.c_str());
7445 if (output ==
"json") {
7449 j[
"layers"] = nlayers;
7452 j[
"seconds"] = best;
7454 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7456 r[
"node"] = names.names[i];
7457 r[
"type"] = ln_element_kind(names, i);
7458 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
7460 else if (sol.
is_bound) r[key] = 0.0;
7461 else r[key] =
nullptr;
7471 emit_document(dump_document(j, 1));
7479 "SolverLN(Solver%s) arith=%s type=%s layers=%zu iterations=%d converged=%d time=%.4fs\n",
7483 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7484 "RespT",
"ResidT",
"Tput");
7485 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7498 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
7499 if (!d[i] && sol.
is_bound) std::snprintf(buf, 24,
"%12.6g", 0.0);
7500 else if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
7503 char q[24], u[24], rr[24], w[24], t[24];
7509 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
7510 ln_element_kind(names, i), q, u, rr, w, t);
7524int run_ln_ldes_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
7525 using namespace line;
7528 if (k.samples) o.
samples = k.samples;
7529 if (k.seed) o.
seed =
static_cast<long>(k.seed);
7535 const std::vector<ldes::engine::LnEntryCdf> cdf =
7538 for (std::size_t e = 0; e < model.
nentries; ++e) {
7543 if (output ==
"json") {
7546 j[
"engine"] =
"native-ln";
7548 for (std::size_t e = 0; e < model.
nentries; ++e) {
7551 reg::Json at = reg::Json::array(), af = reg::Json::array();
7552 for (std::size_t i = 0; i < tt[e].size(); ++i) {
7553 at.push_back(tt[e][i]);
7554 af.push_back(ff[e][i]);
7558 ob[
"observations"] =
static_cast<double>(
7562 j[
"entries"] = rows;
7563 emit_document(dump_document(j, 1));
7567 std::printf(
"SolverLDES(native LN engine) getCdfRespT entries=%zu\n", model.
nentries);
7568 for (std::size_t e = 0; e < model.
nentries; ++e) {
7569 if (tt[e].empty()) {
7570 std::printf(
"%-40s (no observation)\n", model.
names[model.
eshift + e + 1].c_str());
7575 const std::vector<double>& t;
7576 const std::vector<double>& f;
7577 double operator()(
double p)
const {
7578 for (std::size_t i = 0; i < f.size(); ++i)
7579 if (f[i] >= p)
return t[i];
7582 } q = {tt[e], ff[e]};
7583 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g n=%zu\n",
7584 model.
names[model.
eshift + e + 1].c_str(), q(0.25), q(0.50), q(0.75),
7623int run_ln_ldes(
const std::string& file,
const std::string& output,
const Knobs& k) {
7624 using namespace line;
7631 if (k.samples) o.
samples = k.samples;
7632 if (k.seed) o.
seed =
static_cast<long>(k.seed);
7639 const int repeat = k.repeat > 0 ? k.repeat : 1;
7640 double best = 1e300;
7642 for (
int rep = 0; rep < repeat; ++rep) {
7643 const auto t0 = std::chrono::steady_clock::now();
7645 const auto t1 = std::chrono::steady_clock::now();
7646 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
7654 const auto def = [&](std::size_t i, Col c) {
7658 if (output ==
"json") {
7661 j[
"arith"] =
"double";
7662 j[
"solver"] =
"ldes";
7667 j[
"engine"] =
"native-ln";
7672 j[
"seconds"] = best;
7674 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7676 row[
"node"] = model.
names[i];
7677 row[
"type"] = ln_element_kind(model, i);
7678 auto put = [&](
const char* key,
double v,
bool defined) {
7679 if (defined) row[key] = v;
7680 else row[key] =
nullptr;
7682 put(
"QLen", r.
QLN(i, 0), def(i, Col::QLen));
7683 put(
"Util", r.
ULN(i, 0), def(i, Col::Util));
7684 put(
"RespT", r.
RLN(i, 0), def(i, Col::RespT));
7685 put(
"ResidT", r.
WLN(i, 0), def(i, Col::ResidT));
7686 put(
"Tput", r.
TLN(i, 0), def(i, Col::Tput));
7687 rows.push_back(row);
7690 emit_document(dump_document(j, 1));
7694 std::printf(
"SolverLDES(native LN engine) arith=double type=%s samples=%zu seed=%ld "
7695 "simtime=%.6g completions=%lld time=%.4fs\n",
7698 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7699 "RespT",
"ResidT",
"Tput");
7700 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7702 auto fmt = [&](
double v,
bool defined,
char* buf) {
7703 if (!defined) std::snprintf(buf, 24,
"%12s",
"NaN");
7704 else std::snprintf(buf, 24,
"%12.6g", v);
7706 char q[24], u[24], rr[24], w[24], t[24];
7707 fmt(r.
QLN(i, 0), def(i, Col::QLen), q);
7708 fmt(r.
ULN(i, 0), def(i, Col::Util), u);
7709 fmt(r.
RLN(i, 0), def(i, Col::RespT), rr);
7710 fmt(r.
WLN(i, 0), def(i, Col::ResidT), w);
7711 fmt(r.
TLN(i, 0), def(i, Col::Tput), t);
7712 std::printf(
"%-62s %-10s %s %s %s %s %s\n", model.
names[i].c_str(),
7713 ln_element_kind(model, i), q, u, rr, w, t);
7730int run_lqns(
const std::string& file,
const std::string& output,
const Knobs& k) {
7731 using namespace line;
7736 if (!k.method.empty())
opt.method = k.method;
7737 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
7738 if (k.samples)
opt.samples =
static_cast<double>(k.samples);
7739 opt.verbose = k.verbose;
7741 opt.remote = k.remote;
7742 if (!k.remote_url.empty())
opt.remote_url = k.remote_url;
7743 opt.timeout_seconds = k.timeout_seconds;
7750 if (output ==
"json") {
7755 j[
"method"] =
opt.method;
7757 j[
"seconds"] = solver.runtime();
7759 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
7761 r[
"node"] = names.names[i];
7762 r[
"type"] = ln_element_kind(names, i);
7763 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
7765 else r[key] =
nullptr;
7775 emit_document(dump_document(j, 1));
7779 std::printf(
"SolverLQNS(%s) arith=%s type=%s iterations=%d time=%.4fs\n",
7783 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7784 "RespT",
"ResidT",
"Tput");
7785 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
7787 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
7788 if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
7790 std::snprintf(buf, 24,
"%12.6g",
7793 char q[24], u[24], rr[24], w[24], t[24];
7799 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
7800 ln_element_kind(names, i), q, u, rr, w, t);
7806std::string upper_tag(
const std::string& s) {
7807 std::string out = s;
7808 for (std::size_t i = 0; i < out.size(); ++i)
7809 out[i] =
static_cast<char>(std::toupper(
static_cast<unsigned char>(out[i])));
7832int solve_lqn_dispatch(
const std::string& arith,
const std::string& solver,
7833 const std::string& analysis,
const std::string& output,
7834 const std::string& file,
const Knobs& k) {
7837 "a layered model is read from a file: pass -f <model.lqnx> or -f <model.json> (neither "
7838 "layered reader has a stdin form)");
7839 if (analysis !=
"avg" && analysis !=
"tran" && analysis !=
"sens" && analysis !=
"cdf")
7841 "the layered path ports -a avg (getAvgTable), -a tran (getTranAvg), -a sens "
7842 "(getSensitivityTable) and -a cdf (getCdfRespT); got '" + analysis +
"'");
7843 const std::string s = solver.empty() ?
"auto" : solver;
7844 if (s !=
"auto" && s !=
"ln" && s !=
"ln.mva" && s !=
"ln.comom" && s !=
"lqns" &&
7847 "the layered path takes -s ln, ln.mva, ln.comom, ldes, lqns or auto (got '" + s +
7848 "'); a Network solver cannot be applied to a LayeredNetwork directly");
7857 std::string engine = s;
7861 if (s ==
"auto" && kk.layer_solver.empty()) {
7862 std::string getter =
"getAvgTable";
7863 if (analysis ==
"tran") getter =
"getTranAvg";
7864 else if (analysis ==
"cdf") getter =
"getCdfRespT";
7865 else if (analysis ==
"sens") getter =
"getSensitivityTable";
7869 bool has_cache_task =
false;
7870 for (std::size_t i = 0; i < probe.
iscache.size(); ++i)
7871 if (probe.
iscache[i]) has_cache_task =
true;
7875 if (token ==
"lqns") engine =
"lqns";
7876 else if (token ==
"ln.comom") kk.layer_solver =
"nc";
7877 else if (token ==
"ln.fld") kk.layer_solver =
"fluid";
7878 else if (token ==
"ln.mam") kk.layer_solver =
"mam";
7879 else kk.layer_solver =
"mva";
7881 for (std::size_t i = 0; i < lc.
skipped.size(); ++i)
7882 note += std::string(i ?
", " :
" (the ranking preferred ") +
7884 if (!lc.
skipped.empty()) note +=
", which is not available here)";
7885 std::printf(
"SolverAUTO selected %s%s\n", token.c_str(), note.c_str());
7893 if (engine ==
"lqns") {
7894 if (analysis !=
"avg")
7896 "SolverLQNS reports the mean table its binary computes; it has no transient, no "
7897 "sensitivity and no response-time distribution here, so it takes -a avg (got '" +
7899 if (!kk.layer_solver.empty())
7901 "--layer-solver names the engine SolverLN runs on each layer; -s lqns solves no "
7902 "layers, it hands the whole model to the lqns binary");
7903 if (output ==
"layers")
7905 "-o layers dumps the stations and routing SolverLN BUILT from the model; lqns "
7906 "builds its own submodels inside another process and this port never sees them");
7907 if (kk.iter_tol >= 0.0 || kk.iter_max > 0)
7909 "--iter_tol and --iter_max are SolverLN's layer-iteration knobs; lqns runs its own "
7910 "iteration and takes neither (its --iteration-limit is unreliable as of 6.2.27, "
7911 "which is why the reference stopped passing it)");
7914 "--seed sets the stream of a simulator this port drives; lqsim seeds itself and "
7915 "the wrapper passes no seed, exactly as the reference does not");
7918 "--repeat times a solve by re-running it; re-running lqsim would report a "
7919 "different answer under the same banner");
7920 if (kk.no_interlocking || kk.interlock_knobs_given() || !kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
7921 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0 || kk.nlp_knobs_given())
7923 "--no-interlocking, --interlock-*, --ln-transient*, --sens-*, --lld, --nlp-*, --warmstart and "
7924 "--mol-* are SolverLN options; -s lqns has none of them");
7925 if (arith !=
"double")
7927 "SolverLQNS reads a result file another program wrote in decimal double "
7928 "precision; there is no higher precision to carry, so rerun with --arith double "
7929 "(got '" + arith +
"')");
7936 if (kk.has_cutoff())
7938 "--cutoff bounds the open population of a CTMC state space and applies to -s "
7939 "ctmc; the layered path enumerates no states");
7942 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7943 "reports every LQN element");
7944 return run_lqns<double>(file, output, kk);
7946 if (k.keep || k.verbose || k.remote || !k.remote_url.empty() || k.timeout_seconds)
7948 "--keep, --verbose, --remote, --remote-url and --timeout describe the child process "
7949 "of an external solver and apply to -s lqns only");
7956 if (engine ==
"ldes") {
7957 if (analysis !=
"avg" && analysis !=
"cdf")
7959 "the native LN engine measures a sample path: it takes -a avg for the mean table "
7960 "and -a cdf for the per-entry response time distribution, and has no transient "
7961 "and no sensitivity here (got '" + analysis +
"')");
7962 if (arith !=
"double")
7964 "the native LN engine accumulates its estimators in double, so there is no higher "
7965 "precision to carry; rerun with --arith double (got '" + arith +
"')");
7966 if (output ==
"layers")
7968 "-o layers dumps the stations and routing SolverLN BUILT from the model; -s ldes "
7969 "simulates the layered semantics directly and builds no submodels");
7970 if (!kk.layer_solver.empty())
7972 "--layer-solver names the engine SolverLN runs on each layer; -s ldes solves no "
7973 "layers, it simulates entries, activities and calls directly");
7974 if (kk.iter_tol >= 0.0 || kk.iter_max > 0 || kk.no_interlocking || kk.interlock_knobs_given())
7976 "--iter_tol, --iter_max, --no-interlocking and --interlock-* are SolverLN's layer-iteration "
7977 "knobs; a simulated sample path converges by run length, which is --samples");
7978 if (!kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
7979 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0 || kk.nlp_knobs_given())
7981 "--ln-transient*, --sens-*, --lld, --nlp-*, --warmstart and --mol-* are SolverLN options; "
7982 "-s ldes has none of them");
7983 if (!kk.method.empty() && kk.method !=
"default")
7985 "--method on the layered path names the LN UPDATE (default, moment3, mw.*); "
7986 "-s ldes performs no update, it simulates the model (got '" + kk.method +
"')");
7992 if (!kk.ldes_tranfilter.empty() || kk.ldes_warmupfrac >= 0.0 ||
7993 !kk.ldes_cimethod.empty() || kk.ldes_cnvgon || kk.ldes_cnvgtol > 0.0 ||
7994 kk.ldes_slotted || kk.ldes_slotlength > 0.0 || kk.ldes_replications > 0 ||
7995 kk.ldes_numthreads > 0 || kk.ldes_maxtime > 0.0 || !kk.ldes_initsol.empty() ||
7996 !kk.ldes_rest_url.empty())
7998 "the --ldes-* flags configure the SUBPROCESS engine that answers -s ldes on a "
7999 "Network (warmup filter, CI estimator, slot lattice, replications, warm-start "
8000 "placement); the native LN engine behind -i lqnx -s ldes reads --samples and "
8005 if (kk.has_cutoff())
8007 "--cutoff bounds the open population of a CTMC state space and applies to -s "
8008 "ctmc; the layered path enumerates no states");
8011 "--node selects the stateful node a CTMC query is labelled by; the layered path "
8012 "reports every LQN element");
8015 "--tspan sets the horizon of a transient analysis; the native LN engine runs to "
8016 "a completion budget, which is --samples");
8019 "--tol is not an LDES option; the run length is set with --samples");
8020 if (analysis ==
"cdf")
return run_ln_ldes_cdf(file, output, kk);
8021 return run_ln_ldes(file, output, kk);
8025 if (s ==
"ln.comom") {
8026 if (!kk.layer_solver.empty() && kk.layer_solver !=
"nc")
8027 throw line::InputError(
"-s ln.comom already selects NC layers, but --layer-solver says '" +
8028 kk.layer_solver +
"'");
8029 kk.layer_solver =
"nc";
8030 }
else if (s ==
"ln.mva") {
8031 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva")
8032 throw line::InputError(
"-s ln.mva already selects MVA layers, but --layer-solver says '" +
8033 kk.layer_solver +
"'");
8034 kk.layer_solver =
"mva";
8039 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva" && kk.layer_solver !=
"nc" &&
8040 kk.layer_solver !=
"fluid" && kk.layer_solver !=
"ssa" && kk.layer_solver !=
"mam" &&
8041 kk.layer_solver !=
"ctmc" && kk.layer_solver !=
"ldes" && kk.layer_solver !=
"jmt")
8042 throw line::InputError(
"--layer-solver takes mva, nc, fluid, ssa, mam, ctmc, ldes or jmt (got '" +
8043 kk.layer_solver +
"')");
8045 if ((k.samples || k.seed) && kk.layer_solver !=
"ssa" && kk.layer_solver !=
"ldes" &&
8046 kk.layer_solver !=
"jmt")
8048 "--samples and --seed set the run length and the stream of a SIMULATED layer; the "
8049 "layered path draws no random numbers unless --layer-solver ssa, ldes or jmt is in force");
8052 "--cutoff bounds the open population of a CTMC state space and applies to -s ctmc; the "
8053 "layered path enumerates no states");
8054 if (k.t1 >= 0.0 && analysis !=
"tran")
8056 "--tspan sets the horizon of a transient analysis and applies to the layered path "
8057 "only with -a tran");
8058 if (analysis ==
"tran" && !(k.t1 >= 0.0))
8060 "-a tran integrates each layer's drift and needs a horizon: pass --tspan <t0>:<t1>");
8063 "--node selects the stateful node a CTMC query is labelled by; the layered path "
8064 "reports every LQN element");
8067 "--tol is not a SolverLN option (LnOptions carries iter_tol and iter_max); "
8076 static const char*
const ln_methods[] = {
8077 "default",
"moment3",
"mw.upper",
"mw.lower",
"srvn",
"srvn.ph",
"srvn.cs",
8078 "flat",
"flat.cs",
"flat.ph",
"srvncs",
"ph",
"cs",
"flatcs",
"flatph",
8079 "squashed",
"squashed.ph",
"nlp",
"qdamva",
"qd-amva"};
8080 bool known = k.method.empty();
8081 for (
const char* m : ln_methods)
8082 if (k.method == m) known =
true;
8085 "--method on the layered path takes default, srvn, srvn.ph, srvn.cs, flat, "
8086 "flat.cs, flat.ph, moment3, mw.upper, mw.lower or nlp (alias qdamva, qd-amva) (got '" +
8087 k.method +
"'); the per-layer engine is chosen with --layer-solver");
8089 if ((k.method ==
"nlp" || k.method ==
"qdamva" || k.method ==
"qd-amva") &&
8090 (analysis !=
"avg" || output ==
"layers"))
8092 "--method nlp states the model as one program and builds no layers, so it has no transient, "
8093 "no per-layer sensitivity, no response-time law and no layer to dump; use -a avg");
8094 if ((k.method ==
"mw.upper" || k.method ==
"mw.lower") && analysis !=
"avg")
8096 "--method mw.* reports a throughput and utilization BOUND and solves no layer, so "
8097 "it has no transient, no sensitivity and no response-time law; use -a avg");
8098 if (!k.sens_method.empty() && analysis !=
"sens")
8100 if (!k.ln_transient.empty() && analysis !=
"tran")
8103 if (analysis ==
"tran") {
8104 if (arith !=
"double")
8106 "the layered transient integrates each layer's drift with LSODA, which is double "
8107 "precision by construction; rerun with --arith double (got '" + arith +
"')");
8108 return run_ln_tran<double>(file, output, kk);
8110 if (analysis ==
"cdf") {
8111 if (arith !=
"double")
8113 "-a cdf fits an APH to a fluid passage time, integrated by LSODA in double "
8114 "precision; rerun with --arith double (got '" + arith +
"')");
8115 return run_ln_cdf<double>(file, output, kk);
8117 if (analysis ==
"sens") {
8118 if (arith ==
"double")
return run_ln_sens<double>(file, output, kk);
8119 if (arith ==
"exact")
return run_ln_sens<line::Rational>(file, output, kk);
8120 if (arith ==
"real:16")
return run_ln_sens<line::Real<16> >(file, output, kk);
8121 if (arith ==
"real" || arith ==
"real:32")
8122 return run_ln_sens<line::Real<32> >(file, output, kk);
8123 if (arith ==
"real:64")
return run_ln_sens<line::Real<64> >(file, output, kk);
8124 if (arith ==
"real:128")
return run_ln_sens<line::Real<128> >(file, output, kk);
8125 if (arith ==
"real:256")
return run_ln_sens<line::Real<256> >(file, output, kk);
8126 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8129 if (arith ==
"double")
return run_ln<double>(file, output, kk);
8130 if (arith ==
"exact")
return run_ln<line::Rational>(file, output, kk);
8132 if (arith ==
"real:16")
return run_ln<line::Real<16> >(file, output, kk);
8133 if (arith ==
"real" || arith ==
"real:32")
return run_ln<line::Real<32> >(file, output, kk);
8134 if (arith ==
"real:64")
return run_ln<line::Real<64> >(file, output, kk);
8135 if (arith ==
"real:128")
return run_ln<line::Real<128> >(file, output, kk);
8136 if (arith ==
"real:256")
return run_ln<line::Real<256> >(file, output, kk);
8137 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8159 const std::size_t M =
sn.nstations, K =
sn.nclasses;
8168 for (std::size_t j = 0; j < M; ++j)
8169 if (
sn.stations[j].name == a.
station_names[i]) { st_of[i] = j + 1;
break; }
8170 std::vector<std::size_t> cl_of(a.
class_names.size(), 0);
8171 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
8172 for (std::size_t j = 0; j < K; ++j)
8173 if (
sn.classes[j].name == a.
class_names[c]) { cl_of[c] = j + 1;
break; }
8174 for (
int m = 0; m < 6; ++m) {
8177 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
8178 if (st_of[i] && cl_of[c])
8179 (*dst[m])(st_of[i] - 1, cl_of[c] - 1) = ldes_at(*src[m], i, c);
8180 for (std::size_t f = 0; f < F; ++f)
8181 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
8182 if (cl_of[c]) (*dst[m])(M + f, cl_of[c] - 1) = ldes_at(*fcr[m], f, c);
8185 for (std::size_t i = 0; i < M; ++i)
8186 for (std::size_t c = 0; c < K; ++c) RNs(i, c) = r.
RN(i, c);
8188 for (std::size_t i = 0; i < M; ++i)
8189 for (std::size_t c = 0; c < K; ++c) r.
WN(i, c) = WNs(i, c);
8190 for (std::size_t c = 0; c < K; ++c) {
8191 r.
CN.push_back(ldes_at(a.
CN, 0, c));
8192 r.
XN.push_back(ldes_at(a.
XN, 0, c));
8224 const std::string& s, std::string& banner,
8225 std::string* suffix =
nullptr,
8226 const std::string* file =
nullptr) {
8228 if (s ==
"jmt" || s ==
"ldes") {
8232 if constexpr (std::is_same_v<T, double>) {
8235 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
8236 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
8237 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
8243 banner =
"SolverJMT";
8248 std::snprintf(buf,
sizeof(buf),
" samples=%g seed=%ld", o.
samples, o.
seed);
8254 ldes_run(file ? *file : std::string(), o, std::vector<std::string>());
8255 r = avg_result_from_ldes(
sn, a);
8257 banner =
"SolverLDES";
8260 std::snprintf(buf,
sizeof(buf),
" engine=%s samples=%zu seed=%ld",
8268 }
else if (s ==
"ssa" || s ==
"fluid") {
8275 if constexpr (std::is_same_v<T, double>) {
8278 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8279 if (k.samples)
opt.samples = k.samples;
8280 if (k.seed)
opt.seed = k.seed;
8281 if (k.cutoff >= 0.0)
opt.cutoff = k.cutoff;
8286 std::vector<line::ssa::SsaCacheRatio>
cache;
8299 banner =
"SolverSSA";
8306 std::snprintf(buf,
sizeof(buf),
" samples=%zu seed=%lu time=%.6g", a.
samples,
8324 banner =
"SolverFluid";
8348 const bool has_ref = !refreshed.
nodes.empty();
8349 r = avg_result_from_sim<T>(has_ref ? refreshed :
sn, a.
QN, a.
UN, a.
RN, a.
TN, a.
CN,
8355 banner =
"SolverFluid";
8358 std::snprintf(buf,
sizeof(buf),
" iters=%zu", a.
iters);
8365 }
else if (s ==
"nc") {
8367 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8368 if (k.tol >= 0.0)
opt.tol = k.tol;
8369 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
8370 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8371 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
8380 if constexpr (std::is_same_v<T, double>) {
8390 banner =
"SolverNC";
8391 }
else if (s ==
"mam") {
8396 banner =
"SolverMAM";
8397 }
else if (s ==
"ag") {
8399 apply_ag_knobs(k,
opt);
8401 banner =
"SolverAG";
8402 }
else if (s ==
"ba") {
8404 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8406 banner =
"SolverBA";
8407 }
else if (s ==
"ctmc") {
8409 if (!k.method.empty())
opt.method = k.method;
8410 if (k.cutoff >= 0.0)
opt.cutoff = k.cutoff;
8411 opt.cutoff_mat = k.cutoff_mat;
8412 opt.force = k.force;
8413 if (!k.nonmkv.empty())
opt.nonmkv = k.nonmkv;
8416 banner =
"SolverCTMC";
8419 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8420 if (k.tol >= 0.0)
opt.tol = k.tol;
8421 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
8422 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8428 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
8429 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
8432 if constexpr (std::is_same_v<T, double>) {
8444 banner =
"SolverMVA";
8478int solve_model_node(
const std::string& file,
const Knobs& k,
const std::string& s) {
8481 std::string banner, suffix;
8484 const std::size_t I =
sn.nodes.size(), R =
sn.nclasses;
8485 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8486 const line::Matrix<T>&QNn = nm.QN, &UNn = nm.UN, &RNn = nm.RN, &WNn = nm.WN, &ANn = nm.AN,
8502 const std::size_t F =
8503 r.
QN.rows() >
sn.nstations ? r.
QN.rows() -
sn.nstations :
static_cast<std::size_t
>(0);
8504 const double region_nan = std::numeric_limits<double>::quiet_NaN();
8505 auto region_name = [&](std::size_t f) {
8506 return (f <
sn.regions.size() && !
sn.regions[f].name.empty())
8507 ?
sn.regions[f].name
8508 :
"FCR" + std::to_string(f + 1);
8512 auto region_empty = [&](std::size_t f, std::size_t c) {
8513 return !(d(r.
QN(
sn.nstations + f, c)) > 0.0 || d(r.
RN(
sn.nstations + f, c)) > 0.0 ||
8514 d(r.
TN(
sn.nstations + f, c)) > 0.0);
8516 if (g_json_output) {
8522 p[
"type"] =
"AvgNodeTable";
8524 for (
const char* key : {
"Node",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
8526 p[key] = line::reg::Json::array();
8527 for (std::size_t i = 0; i < I; ++i)
8528 for (std::size_t c = 0; c < R; ++c) {
8529 if (hidden[c])
continue;
8532 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
8533 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
8535 p[
"Node"].push_back(
sn.nodes[i].name);
8536 p[
"JobClass"].push_back(
sn.classes[c].name);
8537 p[
"QLen"].push_back(d(QNn(i, c)));
8538 p[
"Util"].push_back(d(UNn(i, c)));
8539 p[
"RespT"].push_back(d(RNn(i, c)));
8540 p[
"ResidT"].push_back(d(WNn(i, c)));
8541 p[
"ArvR"].push_back(d(ANn(i, c)));
8542 p[
"Tput"].push_back(d(TNn(i, c)));
8544 for (std::size_t f = 0; f < F; ++f)
8545 for (std::size_t c = 0; c < R; ++c) {
8546 if (region_empty(f, c))
continue;
8547 p[
"Node"].push_back(region_name(f));
8548 p[
"JobClass"].push_back(
sn.classes[c].name);
8549 p[
"QLen"].push_back(d(r.
QN(
sn.nstations + f, c)));
8550 p[
"Util"].push_back(region_nan);
8551 p[
"RespT"].push_back(d(r.
RN(
sn.nstations + f, c)));
8552 p[
"ResidT"].push_back(d(r.
WN(
sn.nstations + f, c)));
8553 p[
"ArvR"].push_back(region_nan);
8554 p[
"Tput"].push_back(d(r.
TN(
sn.nstations + f, c)));
8561 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Node",
"JobClass",
"QLen",
"Util",
8562 "RespT",
"ResidT",
"ArvR",
"Tput");
8563 for (std::size_t i = 0; i < I; ++i)
8564 for (std::size_t c = 0; c < R; ++c) {
8565 if (hidden[c])
continue;
8566 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
8567 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
8569 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8570 sn.nodes[i].name.c_str(),
sn.classes[c].name.c_str(), d(QNn(i, c)),
8571 d(UNn(i, c)), d(RNn(i, c)), d(WNn(i, c)), d(ANn(i, c)), d(TNn(i, c)));
8573 for (std::size_t f = 0; f < F; ++f)
8574 for (std::size_t c = 0; c < R; ++c) {
8575 if (region_empty(f, c))
continue;
8576 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8577 region_name(f).c_str(),
sn.classes[c].name.c_str(),
8578 d(r.
QN(
sn.nstations + f, c)), region_nan, d(r.
RN(
sn.nstations + f, c)),
8579 d(r.
WN(
sn.nstations + f, c)), region_nan, d(r.
TN(
sn.nstations + f, c)));
8605int solve_model_cache(
const std::string& file,
const Knobs& k,
const std::string& s) {
8608 std::string banner, suffix;
8610 if (r.
cache.empty())
8612 "-a cache reports the per-Cache hit and miss table and this model has no Cache node, "
8613 "or the solver that ran analyzes none; SolverNC's cache branches are what fill it");
8618 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8619 std::size_t srcnode = 0;
8620 for (std::size_t i = 0; i <
sn.nodes.size(); ++i)
8624 std::string node, cls;
8625 double item, list, listcap, items, hitp, dhitp, missp, hitr, dhitr, missr, arvr, residt, cost;
8627 std::vector<Row> rows;
8628 const double dnan = std::numeric_limits<double>::quiet_NaN();
8630 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
8632 const typename std::map<std::size_t, line::qn::CacheParam<T> >::const_iterator it =
8633 sn.nodeparam.find(m.
node);
8634 if (it ==
sn.nodeparam.end())
continue;
8635 const std::vector<std::size_t>& hitclass = it->second.hitclass;
8636 const std::size_t h = m.
itemcap.size();
8637 double totcap = 0.0;
8638 for (std::size_t l = 0; l < h; ++l) totcap += m.
itemcap[l];
8639 double totcost = dnan;
8642 for (std::size_t l = 0; l < m.
listcost.size(); ++l)
8646 for (std::size_t cl = 0; cl <
sn.nclasses; ++cl) {
8647 if (cl >= hitclass.size() || hitclass[cl] == 0)
continue;
8650 if (std::isnan(ph) && std::isnan(pm) && std::isnan(pd))
continue;
8651 if (std::isnan(ph)) ph = 0.0;
8652 if (std::isnan(pm)) pm = 0.0;
8653 if (std::isnan(pd)) pd = 0.0;
8655 if (!std::isfinite(
sn.classes[cl].population)) {
8659 if (cl < it->second.missclass.size() && it->second.missclass[cl] > 0)
8662 const double lat = cache_at(m.
latency, cl);
8665 t.node =
sn.nodes[m.
node - 1].name;
8666 t.cls =
sn.classes[cl].name;
8668 t.item = cl < it->second.classitem.size() ?
static_cast<double>(it->second.classitem[cl]) : 0.0;
8671 t.items =
static_cast<double>(m.
nitems);
8676 t.dhitr = arvr * pd;
8677 t.missr = arvr * pm;
8686 for (std::size_t l = 0; l < m.
hitproblist.cols(); ++l)
8690 for (std::size_t l = 0; l < h; ++l) {
8694 if (std::isnan(phl)) phl = 0.0;
8699 u.list =
static_cast<double>(l + 1);
8705 u.hitr = arvr * phl;
8718 if (g_json_output) {
8720 p[
"type"] =
"AvgCacheTable";
8722 for (
const char* key : {
"Node",
"JobClass",
"Item",
"List",
"ListCap",
"Items",
"HitProb",
8723 "DelayedHitProb",
"MissProb",
"HitRate",
"DelayedHitRate",
8724 "MissRate",
"ArvR",
"ResidT",
"ListCost"})
8725 p[key] = line::reg::Json::array();
8726 for (std::size_t i = 0; i < rows.size(); ++i) {
8727 p[
"Node"].push_back(rows[i].node);
8728 p[
"JobClass"].push_back(rows[i].cls);
8729 p[
"Item"].push_back(rows[i].item);
8730 p[
"List"].push_back(rows[i].list);
8731 p[
"ListCap"].push_back(rows[i].listcap);
8732 p[
"Items"].push_back(rows[i].items);
8733 p[
"HitProb"].push_back(rows[i].hitp);
8734 p[
"DelayedHitProb"].push_back(rows[i].dhitp);
8735 p[
"MissProb"].push_back(rows[i].missp);
8736 p[
"HitRate"].push_back(rows[i].hitr);
8737 p[
"DelayedHitRate"].push_back(rows[i].dhitr);
8738 p[
"MissRate"].push_back(rows[i].missr);
8739 p[
"ArvR"].push_back(rows[i].arvr);
8740 p[
"ResidT"].push_back(rows[i].residt);
8741 p[
"ListCost"].push_back(rows[i].cost);
8748 std::printf(
"%-14s %-12s %5s %5s %8s %6s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
"Node",
8749 "JobClass",
"Item",
"List",
"ListCap",
"Items",
"HitProb",
"DHitProb",
"MissProb",
8750 "HitRate",
"DHitRate",
"MissRate",
"ArvR",
"ResidT",
"ListCost");
8751 for (std::size_t i = 0; i < rows.size(); ++i)
8753 "%-14s %-12s %5g %5g %8g %6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g "
8755 rows[i].node.c_str(), rows[i].cls.c_str(), rows[i].item, rows[i].list, rows[i].listcap,
8756 rows[i].items, rows[i].hitp, rows[i].dhitp, rows[i].missp, rows[i].hitr,
8757 rows[i].dhitr, rows[i].missr, rows[i].arvr, rows[i].residt, rows[i].cost);
8775int solve_model_item(
const std::string& file,
const Knobs& k,
const std::string& s) {
8778 std::string banner, suffix;
8780 if (r.
cache.empty())
8782 "-a item reports the per-item cache occupancy and this model has no Cache node, or "
8783 "the solver that ran analyzes none");
8785 const double dnan = std::numeric_limits<double>::quiet_NaN();
8788 double item, list, listcap, size, prob, cost, dhq, dhqf;
8790 std::vector<Row> rows;
8791 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
8793 const std::size_t h = m.
itemcap.size();
8799 const std::size_t nit =
8801 for (std::size_t i = 0; i < nit; ++i)
8802 for (std::size_t l = 0; l < h; ++l) {
8804 t.node =
sn.nodes[m.
node - 1].name;
8805 t.item =
static_cast<double>(i + 1);
8806 t.list =
static_cast<double>(l + 1);
8811 t.prob = (l + 1) < m.
itemprob.cols()
8814 t.cost = t.size * t.prob;
8826 "-a item needs a per-item occupancy law and this solve produced none; the NC/MVA "
8827 "cache recursions (isolated and integrated alike) and the delayed-hit retrieval "
8828 "algorithms compute the embedded one, SolverCTMC the time-weighted one, and the "
8831 if (g_json_output) {
8833 p[
"type"] =
"AvgItemTable";
8835 for (
const char* key : {
"Node",
"Item",
"List",
"ListCap",
"Size",
"Prob",
"Cost",
8836 "DelayedHitQLen",
"DelayedHitQLenFull"})
8837 p[key] = line::reg::Json::array();
8838 for (std::size_t i = 0; i < rows.size(); ++i) {
8839 p[
"Node"].push_back(rows[i].node);
8840 p[
"Item"].push_back(rows[i].item);
8841 p[
"List"].push_back(rows[i].list);
8842 p[
"ListCap"].push_back(rows[i].listcap);
8843 p[
"Size"].push_back(rows[i].size);
8844 p[
"Prob"].push_back(rows[i].prob);
8845 p[
"Cost"].push_back(rows[i].cost);
8846 p[
"DelayedHitQLen"].push_back(rows[i].dhq);
8847 p[
"DelayedHitQLenFull"].push_back(rows[i].dhqf);
8854 std::printf(
"%-14s %6s %6s %8s %10s %12s %12s %14s %18s\n",
"Node",
"Item",
"List",
"ListCap",
8855 "Size",
"Prob",
"Cost",
"DelayedHitQLen",
"DelayedHitQLenFull");
8856 for (std::size_t i = 0; i < rows.size(); ++i)
8857 std::printf(
"%-14s %6g %6g %8g %10g %12.8g %12.8g %14.8g %18.8g\n", rows[i].node.c_str(),
8858 rows[i].item, rows[i].list, rows[i].listcap, rows[i].size, rows[i].prob,
8859 rows[i].cost, rows[i].dhq, rows[i].dhqf);
8872int solve_model_sys(
const std::string& file,
const Knobs& k,
const std::string& s) {
8875 std::string banner, suffix;
8882 if (g_json_output) {
8888 p[
"type"] =
"AvgSysTable";
8890 for (
const char* key : {
"Chain",
"JobClasses",
"SysRespT",
"SysTput"})
8891 p[key] = line::reg::Json::array();
8892 for (std::size_t c = 0; c <
sn.nchains; ++c) {
8893 p[
"Chain"].push_back(cn[c]);
8894 p[
"JobClasses"].push_back(cc[c]);
8895 p[
"SysRespT"].push_back(d(sys.
CN[c]));
8896 p[
"SysTput"].push_back(d(sys.
XN[c]));
8901 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner.c_str(),
8903 std::printf(
"%-10s %-24s %14s %14s\n",
"Chain",
"JobClasses",
"SysRespT",
"SysTput");
8904 for (std::size_t c = 0; c <
sn.nchains; ++c)
8905 std::printf(
"%-10s %-24s %14.6g %14.6g\n", cn[c].c_str(), cc[c].c_str(), d(sys.
CN[c]),
8912void print_chain_table(
const char* key,
const char* type,
const char* rowlabel,
8913 const std::vector<std::string>& rows,
8914 const std::vector<std::string>& chains,
8915 const std::vector<std::string>& classes,
8917 const char* banner,
const char* arith,
const char* suffix =
"") {
8919 if (g_json_output) {
8925 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
8926 chains.size(), suffix);
8930 for (
const char* c : {rowlabel,
"Chain",
"JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
8932 p[c] = line::reg::Json::array();
8936 for (std::size_t i = 0; i < rows.size(); ++i)
8937 for (std::size_t c = 0; c < chains.size(); ++c) {
8938 p[rowlabel].push_back(rows[i]);
8939 p[
"Chain"].push_back(chains[c]);
8940 p[
"JobClasses"].push_back(classes[c]);
8941 p[
"QLen"].push_back(d(t.
QN(i, c)));
8942 p[
"Util"].push_back(d(t.
UN(i, c)));
8943 p[
"RespT"].push_back(d(t.
RN(i, c)));
8944 p[
"ResidT"].push_back(d(t.
WN(i, c)));
8945 p[
"ArvR"].push_back(d(t.
AN(i, c)));
8946 p[
"Tput"].push_back(d(t.
TN(i, c)));
8948 emit_analysis<T>(key, p, method);
8951 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
8952 chains.size(), suffix);
8953 std::printf(
"%-16s %-10s %-20s %12s %12s %12s %12s %12s %12s\n", rowlabel,
"Chain",
8954 "JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
8955 for (std::size_t i = 0; i < rows.size(); ++i)
8956 for (std::size_t c = 0; c < chains.size(); ++c)
8957 std::printf(
"%-16s %-10s %-20s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8958 rows[i].c_str(), chains[c].c_str(), classes[c].c_str(), d(t.
QN(i, c)),
8959 d(t.
UN(i, c)), d(t.
RN(i, c)), d(t.
WN(i, c)), d(t.
AN(i, c)),
8973int solve_model_chain(
const std::string& file,
const Knobs& k,
const std::string& s) {
8976 std::string banner, suffix;
8979 std::vector<std::string> rows;
8980 for (std::size_t i = 0; i <
sn.nstations; ++i) rows.push_back(
sn.stations[i].name);
8981 print_chain_table<T>(
"chain",
"AvgChainTable",
"Station", rows,
8990int solve_model_nodechain(
const std::string& file,
const Knobs& k,
const std::string& s) {
8993 std::string banner, suffix;
8995 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8998 std::vector<std::string> rows;
8999 for (std::size_t i = 0; i <
sn.nodes.size(); ++i) rows.push_back(
sn.nodes[i].name);
9000 print_chain_table<T>(
"nodechain",
"AvgNodeChainTable",
"Node", rows,
9012int solve_model_fj(
const std::string& file,
const Knobs& k,
const std::string& s) {
9015 std::string banner, suffix;
9021 t = line::reg::Json::object();
9022 for (
const char* key : {
"Fork",
"Join",
"JobClass",
"FJQLen",
"FJRespT",
"FJTput"})
9023 t[key] = line::reg::Json::array();
9024 t[
"type"] =
"AvgFJTable";
9026 if (g_json_output) {
9030 std::printf(
"%-16s %-16s %-14s %14s %14s %14s\n",
"Fork",
"Join",
"JobClass",
"FJQLen",
"FJRespT",
9032 for (std::size_t i = 0; i < t[
"Fork"].size(); ++i)
9033 std::printf(
"%-16s %-16s %-14s %14.6g %14.6g %14.6g\n", t[
"Fork"][i].get<std::string>().c_str(),
9034 t[
"Join"][i].get<std::string>().c_str(), t[
"JobClass"][i].get<std::string>().c_str(),
9035 t[
"FJQLen"][i].get<double>(), t[
"FJRespT"][i].get<double>(), t[
"FJTput"][i].get<double>());
9040inline bool is_avg_view(
const std::string& analysis) {
9041 return analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
9042 analysis ==
"nodechain" || analysis ==
"fj";
9046inline bool nonmkv_reader(
const std::string& s) {
9047 return s ==
"ctmc" || s ==
"mam" || s ==
"fluid" || s ==
"fld" || s ==
"ag";
9060int solve_avg_view(
const std::string& file,
const Knobs& k,
const std::string& s,
9061 const std::string& analysis) {
9062 if (analysis ==
"node")
return solve_model_node<T>(file, k, s);
9063 if (analysis ==
"sys")
return solve_model_sys<T>(file, k, s);
9064 if (analysis ==
"chain")
return solve_model_chain<T>(file, k, s);
9065 if (analysis ==
"fj")
return solve_model_fj<T>(file, k, s);
9066 return solve_model_nodechain<T>(file, k, s);
9069int solve_model_dispatch(
const std::string& arith,
const std::string& solver,
9070 const std::string& analysis,
const std::string& file,
const Knobs& k) {
9071 std::string s = solver.empty() ?
"auto" : solver;
9072 if (s !=
"mva" && s !=
"auto" && s !=
"fluid" && s !=
"fld" && s !=
"nc" && s !=
"mam" &&
9073 s !=
"ag" && s !=
"ba" && s !=
"ssa" && s !=
"ctmc" && s !=
"uq" && s !=
"env" &&
9074 s !=
"lqns" && s !=
"ldes" && s !=
"jmt")
9076 "the model-solving path ports -s mva, nc, ctmc, mam, ag, ba, ssa, fluid, ldes, jmt, "
9077 "uq, env and lqns (got '" + s +
"'); other solvers remain API-only");
9084 if ((k.verbose && s !=
"ldes") || k.remote || !k.remote_url.empty() ||
9085 (k.keep && s !=
"lqns" && s !=
"jmt"))
9087 "--keep, --verbose, --remote and --remote-url describe the child process of an "
9088 "external solver; on this path -s jmt and -s lqns run one and take --keep, -s ldes "
9089 "runs one and takes --verbose (which echoes the resolved engine command line), and "
9090 "no path takes --remote or --remote-url");
9091 if (k.timeout_seconds && s !=
"lqns")
9093 "--timeout is the deadline of an external solver's child process; on this path only "
9094 "-s lqns runs one");
9100 if (!k.fork_join.empty() && s !=
"mva" && s !=
"nc")
9102 "--fork-join selects the fork-join transform of the shared mean-value fixed point "
9103 "and is read by -s mva and -s nc; -s " + s +
9104 " either simulates or enumerates the fork and applies no transform");
9109 if (k.warmupfrac >= 0.0 && s !=
"ssa")
9111 "--warmupfrac discards a leading fraction of a SIMULATED path before the means are "
9112 "taken and is read by -s ssa; -s " + s +
9113 " has no path to discard (the LDES engine takes --ldes-warmupfrac)");
9114 if (k.pstar > 0.0 && s !=
"fluid" && s !=
"fld")
9116 "--pstar is the exponent of the fluid p-norm smoothing of the drift and is read by "
9117 "-s fluid; -s " + s +
" integrates no drift");
9118 if (!k.fluid_boundary.empty() && s !=
"fluid" && s !=
"fld")
9120 "--fluid-boundary picks how the fluid Petri 'dae' route reports a fluid place level held at a "
9121 "bound and is read by -s fluid; -s " + s +
" has no such report");
9123 if (!k.nonmkv.empty() && s !=
"auto" && !nonmkv_reader(s))
9125 "--nonmkv selects the phase-type fit of a non-Markovian law and is read by -s ctmc, "
9126 "mam, fluid and ag; -s " + s +
" runs no such fit");
9127 if ((!k.busy_orders.empty() || !k.busy_subnet.empty()) && analysis !=
"busyperiod")
9129 "--busyperiod and --busyperiod-subnet name the orders and the subnetwork of "
9130 "-a busyperiod; got -a " + analysis);
9144 if (analysis !=
"avg" && analysis !=
"posterior" && analysis !=
"interval")
9146 "SolverUQ ports -a avg (the prior-weighted expectation), -a posterior (the "
9147 "per-design-point table) and -a interval (the support-only range); got '" +
9149 if (k.uq_solver.empty())
9151 "-s uq needs --uq-solver: UQ computes nothing itself, it expands the Prior and "
9152 "runs another solver at each design point (the C++ spelling of UQ(model, "
9153 "@SolverMVA)). Naming one here by default would attribute the numbers to an "
9154 "engine the caller never chose");
9155 if (k.t1 >= 0.0 || k.node || !k.notation.empty())
9157 "--tspan, --node and --notation name a transient horizon, a stateful node and an "
9158 "ODE document; SolverUQ reports steady-state means over a design of models and "
9159 "has none of the three");
9160 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty() ||
9161 k.nlp_knobs_given())
9163 "--no-interlocking, --interlock-*, --repeat, --layer-solver, --lld, --nlp-*, --warmstart and "
9164 "--mol-* are options of the layered solver "
9165 "and apply to -i lqnx; a Network model has no layers to interlock");
9166 if (arith ==
"double")
return solve_model_uq<double>(file, k, analysis);
9167 if (arith ==
"exact")
return solve_model_uq<line::Rational>(file, k, analysis);
9168 if (arith ==
"real:16")
return solve_model_uq<line::Real<16> >(file, k, analysis);
9169 if (arith ==
"real" || arith ==
"real:32")
9170 return solve_model_uq<line::Real<32> >(file, k, analysis);
9171 if (arith ==
"real:64")
return solve_model_uq<line::Real<64> >(file, k, analysis);
9172 if (arith ==
"real:128")
return solve_model_uq<line::Real<128> >(file, k, analysis);
9173 if (arith ==
"real:256")
return solve_model_uq<line::Real<256> >(file, k, analysis);
9174 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9183 if (analysis !=
"avg")
9185 "SolverENV reports -a avg, the environment-blended means; getEnsembleAvg is its "
9186 "only metric entry in the reference too (got '" + analysis +
"')");
9187 if (k.samples || k.seed)
9189 "--samples and --seed describe a simulation; SolverENV iterates a fixed point over "
9190 "transient stage solves and draws nothing");
9195 if (k.has_cutoff() && k.stage_solver !=
"ctmc")
9197 "--cutoff bounds the open population of an enumerated state space and applies to "
9198 "-s env only beside --stage-solver ctmc; the " +
9199 (k.stage_solver.empty() ? std::string(
"fluid") : k.stage_solver) +
9200 " stages of this ensemble enumerate no states");
9201 if (k.node || !k.notation.empty())
9203 "--node and --notation name a stateful node and an ODE document of ONE network; "
9204 "an Environment holds a network per stage and "
9205 "SolverENV reports the blend over them");
9206 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty() ||
9207 k.nlp_knobs_given())
9209 "--no-interlocking, --interlock-*, --repeat, --layer-solver, --lld, --nlp-*, --warmstart and "
9210 "--mol-* are options of the layered solver "
9211 "and apply to -i lqnx; an Environment has stages, not layers");
9214 "--tspan on the ENV path states the transient HORIZON each stage solve integrates "
9215 "to, and every stage starts from its entry state at 0; a nonzero t0 would name a "
9216 "start the coupling has no state for");
9224 const std::string coupling =
9225 (k.method.empty() || k.method ==
"default" || k.method ==
"mean"
9226 || k.method ==
"blend" || k.method ==
"blending")
9229 if (k.tran_points && coupling ==
"statevec")
9231 "--tran-points is the mean-field coupling's quadrature grid; the state-vector "
9232 "coupling carries the whole joint law across a switch and sums over no such grid, "
9233 "so the value would be accepted and never used");
9240 if (coupling ==
"statedep")
9242 "--method statedep makes each environment transition depend on the state its "
9243 "stage is left in, through a rate function per arc that no model.json can carry "
9244 "(the reference cannot serialize resetEnvRatesFun either); it is reachable from "
9245 "the C++ API, through Environment::set_env_rate_reset");
9246 if (k.tran_points && coupling ==
"occupation")
9248 "--tran-points is the mean-field coupling's quadrature grid; --method occupation "
9249 "solves each stage in steady state and integrates the stage law in closed form, "
9250 "so the value would be accepted and never used");
9251 if (!k.env_init.empty() && coupling !=
"occupation")
9253 "--env-init is options.config.env_init, the initial stage law of --method "
9254 "occupation; the '" + coupling +
"' coupling reads the stationary stage law and "
9255 "would accept the value and never use it");
9256 if ((k.tran_points || k.t1 >= 0.0) && (coupling ==
"avg" || coupling ==
"dec"))
9258 "--tran-points and --tspan state the grid and the horizon of a TRANSIENT stage "
9259 "solve; the closed-form limits --method avg and --method dec solve in steady "
9260 "state and carry nothing across a switch, so both would be accepted and never "
9262 if (arith !=
"double" && coupling !=
"statevec")
9264 "SolverENV solves a stage with the fluid analyzer on every method but the "
9265 "state-vector one -- the mean-field coupling transiently, the avg and dec limits "
9266 "in steady state -- and that analyzer is LSODA's and therefore double; --arith " +
9268 " reaches ENV only through the state-vector coupling (--method statevec)");
9269 if (arith ==
"double")
return solve_model_env<double>(file, k);
9270 if (arith ==
"exact")
return solve_model_env<line::Rational>(file, k);
9271 if (arith ==
"real:16")
return solve_model_env<line::Real<16> >(file, k);
9272 if (arith ==
"real" || arith ==
"real:32")
9273 return solve_model_env<line::Real<32> >(file, k);
9274 if (arith ==
"real:64")
return solve_model_env<line::Real<64> >(file, k);
9275 if (arith ==
"real:128")
return solve_model_env<line::Real<128> >(file, k);
9276 if (arith ==
"real:256")
return solve_model_env<line::Real<256> >(file, k);
9277 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9279 if (!k.uq_solver.empty())
9281 "--uq-solver names the engine SolverUQ runs at each design point and applies to -s uq; "
9282 "'" + s +
"' solves one model, not a design of them");
9285 "--tran-points is the resolution of the transient grid SolverENV sums its stage exit "
9286 "metrics over and applies to -s env; '" + s +
"' has no such quadrature");
9292 const bool ldes_knob = !k.ldes_tranfilter.empty() || k.ldes_warmupfrac >= 0.0 ||
9293 !k.ldes_cimethod.empty() || k.ldes_cnvgon || k.ldes_slotted ||
9294 k.ldes_replications > 0 || k.ldes_numthreads > 0 ||
9295 k.ldes_maxtime > 0.0 || !k.ldes_initsol.empty() ||
9296 !k.ldes_rest_url.empty();
9297 if (ldes_knob && s !=
"ldes" && s !=
"auto")
9299 "the --ldes-* flags are the discrete-event engine's own settings (warmup filter, "
9300 "confidence-interval estimator, slot lattice, replications, warm-start placement) and "
9301 "apply to -s ldes; '" + s +
"' has none of them");
9302 if (k.jmt_replications > 0 && s !=
"jmt")
9304 "--jmt-replications is SolverJMT's transient ensemble size (-s jmt -a tran / -a "
9305 "tranprob); '" + s +
"' has none (-s ldes takes --ldes-replications)");
9307 if (arith !=
"double")
9309 "SolverJMT is a client of the Java Modelling Tools engine, which simulates in "
9310 "double and reports in double; --arith " + arith +
9311 " would label a double answer with an arithmetic that never touched it");
9319 if (analysis !=
"avg" && analysis !=
"cdf" && analysis !=
"trancdf" &&
9320 analysis !=
"trancdfpasst" && analysis !=
"prob" && analysis !=
"tran" &&
9321 analysis !=
"tranprob" && analysis !=
"sample" && !is_avg_view(analysis))
9323 "-s jmt reports -a avg (the JSIM or JMVA mean table), its four views -a node, "
9324 "-a sys, -a chain and -a nodechain, -a cdf (the empirical response-time law read "
9325 "back from the JMT logs, preloaded at the rounded steady-state queue lengths), "
9326 "-a tran-cdf-respt and -a tran-cdf-passt (the same logged run from the default "
9327 "initial state, so the samples cover the transient), -a prob (the time each "
9328 "declared state is held for along the logged trajectory), -a tran (the transient "
9329 "means over --jmt-replications independent replications), -a tranprob (the same "
9330 "replications' aggregate state law at one station) and -a sample (one logged "
9331 "trajectory); the DETAILED laws -a states and getTranProb have no counterpart, "
9332 "since a JMT log records per-class job counts and nothing about the buffer order "
9333 "or the service phase");
9339 if (!k.method.empty() &&
9340 std::find(valid.begin(), valid.end(), k.method) == valid.end())
9342 "SolverJMT methods are default, jsim and the jmva family (jmva, jmva.amva, "
9343 "jmva.mva, jmva.recal, jmva.comom, jmva.chow, jmva.bs, jmva.aql, jmva.lin, "
9344 "jmva.dmlin); got '" + k.method +
"'");
9347 const bool jmt_replicated = analysis ==
"tran" || analysis ==
"tranprob";
9348 if (k.iter_max >= 0)
9350 "--iter_max is not a SolverJMT option: the number of independent replications -a "
9351 "tran and -a tranprob average over is --jmt-replications (options.config."
9352 "replications, default 10)");
9353 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || !k.multiserver.empty())
9355 "--tol, --iter_tol and --multiserver configure a fixed-point iteration; JSIM "
9356 "simulates a sample path and JMVA takes its tolerance from the exported document. "
9357 "The simulation's stopping rule is --samples");
9358 if (k.jmt_replications > 0 && !jmt_replicated)
9360 "--jmt-replications sizes the transient ensemble of -a tran and -a tranprob; "
9361 "steady-state JSIM (-a " + analysis +
") is one run and reads no replication count");
9364 "--cutoff truncates an enumerated state space; JMT enumerates none");
9371 const bool jmt_node_arm =
9372 analysis ==
"prob" || analysis ==
"tranprob" || analysis ==
"sample";
9373 if ((k.node && !jmt_node_arm) || k.jobclass || !k.marg_states.empty())
9375 "--node, --class and --marg-states select the marginal law of one (node, class); "
9376 "the JMT arms report tables over every station and class, and --node applies to "
9377 "-a prob (which station --state overrides), -a tranprob (whose state law) and "
9378 "-a sample (whose trajectory)");
9379 if (!k.state.empty() && analysis !=
"prob")
9381 "--state names the state a probability is asked about and applies to -a prob");
9382 if (!k.state.empty() && !k.node)
9384 "--state is the per-class job count of ONE station and needs --node to say "
9385 "which; a bare count vector cannot be matched against a whole network");
9386 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
9388 "--notation, --symbolic and --equilibria describe an exported ODE document; JMT "
9389 "integrates no ODE");
9390 if (!k.cdf_algorithm.empty())
9392 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
9393 "JMT response-time law is the ecdf of the passages its loggers recorded and is "
9394 "not computed from a transform");
9395 if (is_avg_view(analysis))
return solve_avg_view<double>(file, k,
"jmt", analysis);
9396 return solve_model_jmt(file, k, analysis);
9399 if (arith !=
"double")
9401 "SolverLDES is a client of the SSJ engine, which simulates in double and reports "
9402 "in double; --arith " + arith +
9403 " would label a double answer with an arithmetic that never touched it");
9411 if (!k.method.empty() && k.method !=
"default" && k.method !=
"para" && k.method !=
"parallel")
9413 "SolverLDES has the methods 'default' and 'parallel' (alias 'para'; listValidMethods returns "
9414 "exactly those in every codebase); got '" + k.method +
"'");
9416 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0 || !k.multiserver.empty())
9418 "--tol, --iter_tol, --iter_max and --multiserver are the knobs of a fixed-point "
9419 "iteration; LDES simulates a sample path and iterates nothing. Its stopping rule "
9420 "is --samples, or --ldes-cnvgon with --ldes-cnvgtol");
9423 "--cutoff truncates an enumerated state space; a simulator visits the states the "
9424 "sample path reaches and enumerates none");
9425 if (k.jobclass || !k.marg_states.empty() || (k.node && analysis !=
"prob"))
9427 "--class and --marg-states select a marginal law of one (node, class), and --node "
9428 "applies to -a prob beside --state; the other LDES arms report tables over every "
9429 "station and class");
9430 if (!k.state.empty() && (analysis !=
"prob" || !k.node))
9432 "--state is getProbAggr(node, state)'s per-class job count of ONE station: it "
9433 "applies to -s ldes -a prob and needs --node to say which");
9434 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
9436 "--notation, --symbolic and --equilibria describe an exported ODE document; LDES "
9437 "integrates no ODE");
9438 if (!k.cdf_algorithm.empty())
9440 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
9441 "LDES response-time law is the ecdf of the samples the engine recorded and is not "
9442 "computed from a transform");
9443 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty() ||
9444 k.nlp_knobs_given())
9446 "--no-interlocking, --interlock-*, --repeat, --layer-solver, --lld, --nlp-*, --warmstart and "
9447 "--mol-* are options of the layered solver "
9448 "and apply to -i lqnx; the LDES layered path runs in the JAR's own ensemble "
9449 "backend and has no JSON interface to reach from here");
9450 if (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step >= 0.0)
9452 "--sens-method, --sens-scheme and --sens-step configure the layered sensitivity "
9453 "table; LDES reports no sensitivity");
9454 if (analysis ==
"tran" && !(k.t1 >= 0.0))
9456 "-s ldes -a tran needs --tspan <t1> or --tspan <t0>:<t1>: a trajectory over an unstated "
9457 "horizon is not a quantity, and the engine only records buckets once a timespan "
9458 "makes the run transient");
9459 if (k.t1 >= 0.0 && analysis !=
"tran")
9461 "--tspan names the horizon of -a tran; -a sample runs over [0, --samples], which "
9462 "is the horizon runTransientJson uses, and the other arms are steady state");
9469 if ((analysis ==
"trancdf" || analysis ==
"trancdfpasst") &&
9470 k.verbosity !=
"silent")
9471 std::fprintf(stderr,
9472 "Warning: -a %s is the ecdf of the per-job response times the run "
9473 "observed, the same curve -a cdf reports; the reference's LDES "
9474 "getTranCdfRespT reads the same samples\n",
9479 if ((kldes.method ==
"parallel" || kldes.method ==
"para") && kldes.ldes_replications <= 1)
9480 kldes.ldes_replications = 8;
9481 if (is_avg_view(analysis))
return solve_avg_view<double>(file, kldes,
"ldes", analysis);
9482 return solve_model_ldes(file, kldes, analysis);
9492 if (analysis ==
"aoi")
9494 "-a aoi is the AoI branch of the fluid 'mfq' method and no other engine reports "
9495 "it, so SolverAUTO does not choose for it: ask for it by name with -s fluid");
9496 const AutoPlan plan = choose_auto_plan_dispatch(arith, file, analysis, k.method);
9503 if (plan.order.size() == 1) {
9508 kk.method = plan.method;
9509 if (!nonmkv_reader(plan.order[0])) kk.nonmkv.clear();
9510 if (!plan.stage.empty() && !plan.stage[0].empty() && kk.stage_solver.empty())
9511 kk.stage_solver = plan.stage[0];
9512 std::printf(
"SolverAUTO selected %s%s\n", plan.order[0].c_str(), plan.note.c_str());
9513 return solve_model_dispatch(arith, plan.order[0], analysis, file, kk);
9515 std::string first_error, first_category, first_kind;
9516 for (std::size_t i = 0; i < plan.order.size(); ++i) {
9518 kk.method = (i == 0) ? plan.method : std::string();
9519 if (!nonmkv_reader(plan.order[i])) kk.nonmkv.clear();
9521 if (i < plan.stage.size() && !plan.stage[i].empty() && kk.stage_solver.empty())
9522 kk.stage_solver = plan.stage[i];
9524 std::printf(
"SolverAUTO selected %s%s\n", plan.order[i].c_str(),
9527 std::printf(
"SolverAUTO retrying with %s\n", plan.order[i].c_str());
9529 return solve_model_dispatch(arith, plan.order[i], analysis, file, kk);
9530 } catch (
const InterruptRequested&) {
9537 const std::string cat = e.
category();
9538 if (cat ==
"input" || cat ==
"internal")
throw;
9540 std::fprintf(stderr,
9541 "line-cli: warning: SolverAUTO candidate %s failed with an "
9542 "unclassified error [%s] %s; trying the next candidate\n",
9543 plan.order[i].c_str(), e.
id().c_str(), e.what());
9544 if (first_error.empty()) {
9545 first_error = plan.order[i] +
": " + e.what();
9546 first_category = cat;
9547 first_kind = e.
kind();
9549 std::printf(
"SolverAUTO: %s cannot serve this run ([%s] %s)\n",
9550 plan.order[i].c_str(), e.
id().c_str(), e.what());
9551 }
catch (
const std::exception& e) {
9553 std::fprintf(stderr,
9554 "line-cli: warning: SolverAUTO candidate %s failed with a non-LINE "
9555 "exception (%s); trying the next candidate\n",
9556 plan.order[i].c_str(), e.what());
9557 if (first_error.empty()) first_error = plan.order[i] +
": " + e.what();
9558 std::printf(
"SolverAUTO: %s cannot serve this run (%s)\n", plan.order[i].c_str(),
9563 const std::string all_failed =
9564 "SolverAUTO: every candidate refused this run. The chosen engine reported -- " +
9566 if (first_category ==
"numerical")
9568 if (first_category ==
"external")
9570 if (first_category ==
"unsupported")
9578 const bool is_sim = (s ==
"ssa");
9586 const bool is_cftp =
9587 (s ==
"nc" && (k.method ==
"cftp" || k.method ==
"cftp.approx"));
9602 const bool draws_samples =
9603 is_sim || (s ==
"ctmc" && analysis ==
"sample") || is_cftp || nc_draws;
9604 if (k.samples && !draws_samples)
9606 "-s ctmc -a sample and to the stochastic -s nc methods "
9607 "(mci, imci, ls, is, sampling, mcmc, cftp, cftp.approx); '" +
9608 s +
"' has no sample count");
9609 if (k.seed && !draws_samples)
9611 "-s ctmc -a sample and to the stochastic -s nc methods "
9612 "(mci, imci, ls, is, sampling, mcmc, cftp, cftp.approx); '" +
9613 s +
"' draws no random numbers");
9617 if ((k.mdd_tol > 0.0 || k.mdd_maxiter > 0) && !(s ==
"ctmc" && k.method ==
"mdd"))
9619 "--mdd-tol and --mdd-maxiter set the coupled level iteration of -s ctmc --method mdd; "
9620 "'" + s +
" / " + (k.method.empty() ? std::string(
"default") : k.method) +
9621 "' iterates no levels");
9630 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")) &&
9631 !(s ==
"fluid" || s ==
"fld") && !(s ==
"mam" && analysis ==
"tran"))
9633 "--tspan sets the horizon of a transient analysis and applies to -s ctmc -a tran, "
9634 "-a tranprob and -a tranreward, to -s mam -a tran and to -s fluid; '" + s +
" / " +
9635 analysis +
"' integrates no forward equation");
9643 if (k.node && !(s ==
"ctmc" && (analysis ==
"tranprob" || analysis ==
"sample")) &&
9644 !(s ==
"ctmc" && analysis ==
"prob" && !k.state.empty()) &&
9645 !(s ==
"nc" && analysis ==
"prob" && !k.state.empty()) &&
9646 !(s ==
"ssa" && analysis ==
"sample") && !(s ==
"mam" && analysis ==
"prob") &&
9647 !((s ==
"mva" || s ==
"nc") && analysis ==
"marg"))
9649 "--node selects the stateful node a state query is labelled by and applies to -s ctmc "
9650 "-a tranprob and -a sample, to -s ctmc|nc -a prob beside --state, to -s ssa -a sample, "
9651 "to -s mam -a prob and to -s mva|nc -a marg; '" + s +
" / " + analysis +
9652 "' reports the whole network");
9656 if ((k.jobclass || !k.marg_states.empty()) && !(s ==
"mva" && analysis ==
"marg"))
9658 "--class and --marg-states are the job class and the state list of getProbMarg and "
9659 "apply to -s mva -a marg; '" + s +
" / " + analysis +
9660 "' reports every class over its own range");
9664 if (!k.notation.empty() && !((s ==
"fluid" || s ==
"fld") && analysis ==
"odes"))
9666 "--notation selects the form of the exported ODE document and applies to -s fluid -a "
9667 "odes; '" + s +
" / " + analysis +
"' exports no equations");
9670 if ((!k.symbolic.empty() || k.equilibria) &&
9671 !((s ==
"fluid" || s ==
"fld") && analysis ==
"jacobian"))
9673 "--symbolic selects the computer-algebra backend and --equilibria asks it for the "
9674 "solutions of f(x) = 0; both apply to -s fluid -a jacobian, and '" + s +
" / " +
9675 analysis +
"' consults no backend");
9684 if (!k.state.empty() &&
9685 !(analysis ==
"prob" && (s ==
"ctmc" || s ==
"auto" || s ==
"jmt" || s ==
"nc")))
9687 "--state names the state `getProb(node, state)` asks about and applies to -a prob "
9688 "under -s ctmc, -s nc and -s jmt, the arms whose answer is indexed by a state; '" + s +
9689 " / " + analysis +
"' reports a mean or a law over all of them");
9690 if (k.events && analysis !=
"sample")
9692 "--events is the length of ONE sampled trajectory and applies to -a sample; use "
9693 "--samples for a solver's run length ('" + s +
" / " + analysis +
"')");
9694 if (!k.percentiles.empty() && !(s ==
"mam" && (analysis ==
"cdf" || analysis ==
"cdfpasst" ||
9695 analysis ==
"perct")))
9697 "--percentiles names the levels getPerctRespT is read at and applies to -s mam -a "
9698 "perct-respt (and to the percentiles printed beside -a cdf); '" + s +
" / " +
9699 analysis +
"' inverts no response-time law");
9700 if (!k.reward_name.empty() && analysis !=
"rewardvalue")
9702 "--reward-name selects which declared reward -a reward-value returns the value "
9703 "function of; -a reward returns every reward's steady-state expectation and needs no "
9704 "name ('" + s +
" / " + analysis +
"')");
9705 if (k.timestep > 0.0 &&
9707 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
9709 "--timestep is the fixed output grid of a transient CTMC solve, `options.timestep` of "
9710 "ctmc_transient.m, and applies to -s ctmc -a tran, -a tranprob and -a tranreward; the "
9712 "and simulated transients report the points their own integrator or engine produced "
9713 "('" + s +
" / " + analysis +
"')");
9714 if ((!k.rate_sched.empty() || k.ctmc_tv_ngrid > 0) && !(s ==
"ctmc" && analysis ==
"tran"))
9716 "--rate-sched (and --ctmc-tv-ngrid) makes the CTMC generator time-inhomogeneous, "
9717 "`options.config.rate_sched` of solver_ctmc_transient_analyzer.m, and applies to "
9718 "-s ctmc -a tran only ('" + s +
" / " + analysis +
"')");
9719 if ((!k.transient_method.empty() || k.fau_epsilon > 0.0 || k.fau_delta >= 0.0) &&
9721 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
9723 "--transient-method (and --fau-epsilon / --fau-delta) selects how the CTMC forward "
9724 "equation is advanced, `options.config.transient_method` of "
9725 "solver_ctmc_transient_analyzer.m, and applies to -s ctmc -a tran, -a tranprob and "
9726 "-a tranreward; every other analysis solves no forward equation ('" +
9727 s +
" / " + analysis +
"')");
9731 if (!k.cdf_algorithm.empty() && !(s ==
"nc" && analysis ==
"cdf"))
9733 "--cdf-algorithm selects the sojourn-time inversion of the NC response-time "
9734 "distribution and applies to -s nc -a cdf; '" + s +
" / " + analysis +
9735 "' inverts no generating function");
9740 const bool passage_arm =
9741 (s ==
"ctmc" && (analysis ==
"firstpasst" || analysis ==
"firstpasstmom"));
9742 if ((!k.passage_from.empty() || !k.passage_into.empty() || k.passage_orders > 0 ||
9743 !k.passage_taboo.empty()) &&
9746 "--passage-from, --passage-into, --passage-taboo and --passage-orders name the state "
9747 "sets and the moment order of -s ctmc -a firstpasst / firstpasstmom; '" + s +
" / " +
9748 analysis +
"' times no state-set passage");
9750 if (!k.passage_tset.empty() && !(s ==
"ctmc" && analysis ==
"firstpasst"))
9752 "--passage-tset is the time grid of -s ctmc -a firstpasst; '" + s +
" / " +
9753 analysis +
"' evaluates no passage-time curve");
9754 if (!k.passage_method.empty() && !(s ==
"ctmc" && analysis ==
"firstpasst"))
9756 "--passage-method selects the transform inversion of -s ctmc -a firstpasst; '" + s +
9757 " / " + analysis +
"' inverts none (the moments arm solves for them directly)");
9760 if (k.method_perm !=
"exact" && !(s ==
"nc" && analysis ==
"sysmarg"))
9762 "--perm-engine selects the permanent estimator of the NC joint total-queue-length "
9763 "law and applies to -s nc -a sysmarg; '" + s +
" / " + analysis +
9764 "' evaluates no permanent");
9770 const bool nc_sens = s ==
"nc" && analysis ==
"sens";
9771 if (!nc_sens && (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step > 0.0))
9773 "--sens-method, --sens-scheme and --sens-step select the branch of a sensitivity "
9774 "table and apply to -i lqnx -a sens or to -s nc -a sens; '" + s +
" / " + analysis +
9775 "' differentiates nothing");
9776 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty() ||
9777 !k.ln_transient.empty() || !k.ln_transient_channels.empty() || k.nlp_knobs_given())
9779 "--no-interlocking, --interlock-*, --repeat, --layer-solver, --ln-transient*, --lld, --nlp-*, "
9780 "--warmstart and --mol-* are options of the layered solver and apply to -i lqnx; a Network "
9781 "model has no layers to interlock");
9782 if (s ==
"ba" && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
9784 "--tol, --iter_tol and --iter_max do not apply to -s ba: a bound is a closed form, "
9785 "with nothing to converge");
9789 if (s !=
"ba" && (!k.qrf_params.empty() || !k.qrf_alpha.empty()))
9791 "--qrf-params and --qrf-alpha parameterise the QRF reduction bounds and apply to "
9792 "-s ba; '" + s +
"' solves no reduction program");
9793 if (s !=
"ba" && k.level > 0)
9795 "--level is the hierarchy level of the SolverBA bound families and applies to -s ba; "
9796 "'" + s +
"' has no bound hierarchy");
9797 if (is_sim && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
9799 "--tol, --iter_tol and --iter_max do not apply to -s ssa: a sample path is not an "
9800 "iteration; use --samples to set its length");
9801 if (s ==
"mam" && k.iter_tol >= 0.0)
9803 "--iter_tol is not a SolverMAM option (MamOptions carries tol and iter_max); "
9805 if (s ==
"ag" && k.iter_tol >= 0.0)
9807 "--iter_tol is not a SolverAG option (AgOptions carries tol and iter_max, the "
9808 "tolerance and the sweep budget of the reversed-rate fixed point); use --tol");
9815 if (k.max_states >= 0 && s !=
"ag")
9817 "--max-states truncates the queue-length dimension of a SolverAG agent and applies "
9818 "to -s ag; '" + s +
"' truncates no agent (use --cutoff for a CTMC state space)");
9824 if (k.has_cutoff() && s !=
"ctmc" && !(s ==
"mam" && analysis ==
"prob") && s !=
"env")
9826 "--cutoff bounds the open population of a CTMC state space, and the level truncation "
9827 "of -s mam -a prob; '" + s +
" / " + analysis +
"' enumerates no states");
9833 if (!k.cutoff_mat.empty() && s !=
"ctmc")
9835 "--cutoff as a per-(station,class) matrix bounds an enumerated state space per "
9836 "station and applies to -s ctmc; '" + s +
"' takes one number");
9841 if (!k.env_init.empty() && s !=
"env")
9843 "--env-init is the initial stage law of a random environment (SolverENV method "
9844 "'occupation') and applies to -s env; '" + s +
"' has no stages");
9845 if (!k.stage_solver.empty() && s !=
"env")
9847 "--stage-solver names the solver each stage of a random environment is run with and "
9848 "applies to -s env; '" + s +
"' has no stages");
9851 if (!k.stage_solver.empty() && k.stage_solver !=
"fluid" && k.stage_solver !=
"ctmc" &&
9852 k.stage_solver !=
"mam" && k.stage_solver !=
"mva" && k.stage_solver !=
"nc")
9854 "--stage-solver '" + k.stage_solver +
9855 "' is not available: the stage solvers of this port are fluid, ctmc, mam (statevec "
9856 "only), mva and nc (the 'avg'/'dec' limits only)");
9860 if (!k.map_env.empty() && k.map_env !=
"auto" && k.map_env !=
"default" && k.map_env !=
"off")
9862 "' is not a value: use 'auto' (or 'default') or 'off'");
9863 if (!k.map_env_method.empty() && k.map_env_method !=
"auto" && k.map_env_method !=
"dec" &&
9864 k.map_env_method !=
"avg" && k.map_env_method !=
"meanfield")
9866 "--map-env-method '" + k.map_env_method +
9867 "' is not an environment recombination: use 'auto', 'meanfield', 'dec' or 'avg'");
9868 if ((!k.map_env.empty() || !k.map_env_method.empty() || k.map_env_maxstages) &&
9869 (s ==
"ba" || s ==
"env"))
9871 "--map-env* asks a solver to fall back on a random-environment image of a non-renewal "
9872 "process; '-s " + s +
9873 "' takes no such fallback (a bound must not be computed on an approximation, and the "
9874 "environment solver already takes a model with stages)");
9879 if (k.stage_solver ==
"mam" && k.method !=
"statevec")
9881 "--stage-solver mam applies to -s env --method statevec: the LD-QBD backend flattens "
9882 "its blocks into a generator the state-vector coupling propagates a distribution "
9883 "across, and the mean-field coupling carries marginal MEANS instead");
9888 if ((k.fj_accuracy > 0 || !k.fj_tmode.empty()) && s !=
"mam")
9890 "--fj-accuracy and --fj-tmode configure the FJ_codes fork-join approximation of "
9891 "solver_mam_fj.m and apply to -s mam; '" + s +
"' does not run it");
9894 if (k.sync_phases >= 0 && (s !=
"mam" || (k.method !=
"sync.faster" && k.method !=
"sync.slower")))
9896 "--sync-phases sets the synchronous phase expansion of -s mam --method sync.faster or "
9897 "sync.slower; it is not read by '" + s + (k.method.empty() ?
"" :
"." + k.method) +
"'");
9900 if (k.mapmsg_quantization >= 0 && s !=
"mam")
9902 "--mapmsg-quantization sets the patience regime count of the -s mam reneging "
9903 "analyzer; '" + s +
"' does not run it");
9908 if (!k.timescale.empty() && s !=
"mam")
9910 "--timescale selects the time scale of the MAM discrete-time path and applies to "
9911 "-s mam; '" + s +
"' does not read it (SolverNC takes --slotted)");
9930 if (analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
9931 analysis ==
"nodechain" || analysis ==
"cache" || analysis ==
"item" || analysis ==
"fj") {
9932 const bool is_sim_engine = (s ==
"ssa" || s ==
"fluid");
9952 const bool cache_table = (analysis ==
"cache" || analysis ==
"item");
9953 const bool sim_cache_ok = (analysis ==
"cache");
9956 const bool ag_view = (s ==
"ag" && !cache_table);
9957 if ((s !=
"mva" && s !=
"auto" && s !=
"nc" && s !=
"mam" && s !=
"ba" && s !=
"ctmc" &&
9958 !ag_view && !is_sim_engine) ||
9959 (cache_table && is_sim_engine && !sim_cache_ok))
9965 ? std::string(
"-a node reports the per-node table of -s mva, nc, mam, ag, ba, "
9966 "ctmc, ssa, fluid, jmt, ldes and auto; '")
9967 :
"-a " + analysis +
9968 " is a view of the station AvgResult and is reported by -s mva, nc, "
9969 "mam, " + (analysis ==
"item" ?
"" :
"ag, ") +
"ba, ctmc" +
9970 (analysis ==
"item" ?
"" :
", ssa, fluid, jmt, ldes") +
" and auto; '") +
9971 s +
"' does not return the station AvgResult it is built from");
9977 if (is_sim_engine && arith !=
"double")
9979 "-a " + analysis +
" under -s " + s +
9980 " is read off a " + (s ==
"ssa" ?
"sample path" :
"fluid trajectory") +
9981 ", which is transcendental; rerun with --arith double (got '" + arith +
"')");
9982 const std::string eng = (s ==
"auto") ? std::string(
"mva") : s;
9983#define LINE_CLI_TABLE_LADDER(FN) \
9985 if (arith == "double") return FN<double>(file, k, eng); \
9986 if (arith == "exact") return FN<line::Rational>(file, k, eng); \
9987 if (arith == "real:16") return FN<line::Real<16> >(file, k, eng); \
9988 if (arith == "real" || arith == "real:32") return FN<line::Real<32> >(file, k, eng); \
9989 if (arith == "real:64") return FN<line::Real<64> >(file, k, eng); \
9990 if (arith == "real:128") return FN<line::Real<128> >(file, k, eng); \
9991 if (arith == "real:256") return FN<line::Real<256> >(file, k, eng); \
10000#undef LINE_CLI_TABLE_LADDER
10001 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10010 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"gen" && analysis !=
"states" &&
10011 analysis !=
"tran" && analysis !=
"tranprob" && analysis !=
"tranreward" &&
10012 analysis !=
"sample" && analysis !=
"reward" && analysis !=
"rewardvalue" &&
10013 analysis !=
"cdf" && analysis !=
"sens" && analysis !=
"firstpasst" &&
10014 analysis !=
"firstpasstmom")
10016 "the CTMC solver ports -a avg, node, sys, chain, nodechain, prob, gen, states, "
10017 "tran, tranprob, tranreward, sample, reward, reward-value, cdf, first-passt, "
10018 "first-passt-moments and sens (got '" + analysis +
"')");
10024 if (k.method ==
"mdd" && analysis !=
"avg")
10026 "the '" + k.method +
10027 "' method never builds the explicit generator, so it serves -a avg only (got '" +
10028 analysis +
"'); use --method default for the state-space analyses");
10029 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0)
10031 "--tol, --iter_tol and --iter_max do not apply to -s ctmc: the stationary vector "
10032 "is obtained by a direct solve of pi Q = 0, with nothing to converge. The mdd "
10033 "method's level iteration has --mdd-tol and --mdd-maxiter of its own");
10037 if (arith ==
"double")
return solve_model_ctmc<double>(file, k, analysis);
10038 if (arith ==
"exact")
return solve_model_ctmc<line::Rational>(file, k, analysis);
10039 if (arith ==
"real:16")
return solve_model_ctmc<line::Real<16> >(file, k, analysis);
10040 if (arith ==
"real" || arith ==
"real:32")
10041 return solve_model_ctmc<line::Real<32> >(file, k, analysis);
10042 if (arith ==
"real:64")
return solve_model_ctmc<line::Real<64> >(file, k, analysis);
10043 if (arith ==
"real:128")
return solve_model_ctmc<line::Real<128> >(file, k, analysis);
10044 if (arith ==
"real:256")
return solve_model_ctmc<line::Real<256> >(file, k, analysis);
10045 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10057 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"cdf" &&
10058 analysis !=
"cdfpasst" && analysis !=
"perct" && analysis !=
"tran" &&
10059 analysis !=
"internals")
10061 "the MAM solver ports -a avg, node, prob, cdf, cdf-passt, perct-respt, tran and "
10062 "internals (got '" + analysis +
"')");
10063 if (arith !=
"double")
10065 "the MAM solver fits phase-type representations, whose fitter requires "
10066 "transcendental arithmetic; rerun with --arith double (got '" + arith +
"')");
10067 if (analysis ==
"tran" && k.t1 < 0.0)
10069 "-s mam -a tran integrates the transient queue length over a horizon and there is "
10070 "no default for it; pass --tspan t0 t1");
10071 if (analysis ==
"prob")
return solve_model_mam_prob<double>(file, k);
10072 if (analysis ==
"cdf")
return solve_model_mam_cdf<double>(file, k,
"cdf",
"CdfRespT");
10079 if (analysis ==
"cdfpasst")
10080 return solve_model_mam_cdf<double>(file, k,
"cdfpasst",
"CdfPassT");
10081 if (analysis ==
"perct")
return solve_model_mam_perct<double>(file, k);
10082 if (analysis ==
"tran")
return solve_model_mam_tran<double>(file, k);
10083 if (analysis ==
"internals")
return solve_model_mam_internals<double>(file, k);
10084 return solve_model_mam<double>(file, k);
10092 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"sample")
10095 if (arith !=
"double")
10097 "an SSA sample path is generated from exponential clocks, which are "
10098 "transcendental; rerun with --arith double (got '" + arith +
"')");
10099 if (analysis ==
"prob")
return solve_model_ssa_prob<double>(file, k);
10100 if (analysis ==
"sample")
return solve_model_ssa_sample<double>(file, k);
10101 return solve_model_ssa<double>(file, k);
10104 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"marg" &&
10105 analysis !=
"sysmarg" && analysis !=
"cdf" && analysis !=
"sens" &&
10106 analysis !=
"normconst" && analysis !=
"busyperiod")
10108 "the NC solver ports -a avg, -a node, -a prob, -a marg, -a sysmarg, -a cdf, "
10109 "-a sens, -a normconst and -a busyperiod (got '" + analysis +
"')");
10114 if ((k.method ==
"cftp" || k.method ==
"cftp.approx") && analysis !=
"avg")
10116 "the '" + k.method +
10117 "' method draws stationary states and yields no normalizing constant, so it "
10118 "serves -a avg only (got '" + analysis +
"'); use another --method for the "
10119 "other NC analyses");
10120 if (analysis ==
"busyperiod") {
10121 if (arith ==
"double")
return solve_model_nc_busyp<double>(file, k);
10122 if (arith ==
"exact")
return solve_model_nc_busyp<line::Rational>(file, k);
10123 if (arith ==
"real:16")
return solve_model_nc_busyp<line::Real<16> >(file, k);
10124 if (arith ==
"real" || arith ==
"real:32")
10125 return solve_model_nc_busyp<line::Real<32> >(file, k);
10126 if (arith ==
"real:64")
return solve_model_nc_busyp<line::Real<64> >(file, k);
10127 if (arith ==
"real:128")
return solve_model_nc_busyp<line::Real<128> >(file, k);
10128 if (arith ==
"real:256")
return solve_model_nc_busyp<line::Real<256> >(file, k);
10129 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10131 if (analysis ==
"sysmarg") {
10132 if (arith ==
"double")
return solve_model_nc_sysmarg<double>(file, k);
10133 if (arith ==
"exact")
return solve_model_nc_sysmarg<line::Rational>(file, k);
10134 if (arith ==
"real:16")
return solve_model_nc_sysmarg<line::Real<16> >(file, k);
10135 if (arith ==
"real" || arith ==
"real:32")
10136 return solve_model_nc_sysmarg<line::Real<32> >(file, k);
10137 if (arith ==
"real:64")
return solve_model_nc_sysmarg<line::Real<64> >(file, k);
10138 if (arith ==
"real:128")
return solve_model_nc_sysmarg<line::Real<128> >(file, k);
10139 if (arith ==
"real:256")
return solve_model_nc_sysmarg<line::Real<256> >(file, k);
10140 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10142 if (analysis ==
"marg") {
10143 if (arith ==
"double")
return solve_model_nc_marg<double>(file, k);
10144 if (arith ==
"exact")
return solve_model_nc_marg<line::Rational>(file, k);
10145 if (arith ==
"real:16")
return solve_model_nc_marg<line::Real<16> >(file, k);
10146 if (arith ==
"real" || arith ==
"real:32")
10147 return solve_model_nc_marg<line::Real<32> >(file, k);
10148 if (arith ==
"real:64")
return solve_model_nc_marg<line::Real<64> >(file, k);
10149 if (arith ==
"real:128")
return solve_model_nc_marg<line::Real<128> >(file, k);
10150 if (arith ==
"real:256")
return solve_model_nc_marg<line::Real<256> >(file, k);
10151 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10155 if (analysis ==
"normconst") {
10156 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
10157 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
10158 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
10159 if (arith ==
"real" || arith ==
"real:32")
10160 return solve_model_normconst<line::Real<32> >(file, k, s);
10161 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
10162 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
10163 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
10164 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10166 if (analysis ==
"sens") {
10167 if (arith ==
"double")
return solve_model_nc_sens<double>(file, k);
10168 if (arith ==
"exact")
return solve_model_nc_sens<line::Rational>(file, k);
10169 if (arith ==
"real:16")
return solve_model_nc_sens<line::Real<16> >(file, k);
10170 if (arith ==
"real" || arith ==
"real:32")
10171 return solve_model_nc_sens<line::Real<32> >(file, k);
10172 if (arith ==
"real:64")
return solve_model_nc_sens<line::Real<64> >(file, k);
10173 if (arith ==
"real:128")
return solve_model_nc_sens<line::Real<128> >(file, k);
10174 if (arith ==
"real:256")
return solve_model_nc_sens<line::Real<256> >(file, k);
10175 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10177 if (analysis ==
"cdf") {
10178 if (arith ==
"double")
return solve_model_nc_cdf<double>(file, k);
10179 if (arith ==
"exact")
return solve_model_nc_cdf<line::Rational>(file, k);
10180 if (arith ==
"real:16")
return solve_model_nc_cdf<line::Real<16> >(file, k);
10181 if (arith ==
"real" || arith ==
"real:32")
10182 return solve_model_nc_cdf<line::Real<32> >(file, k);
10183 if (arith ==
"real:64")
return solve_model_nc_cdf<line::Real<64> >(file, k);
10184 if (arith ==
"real:128")
return solve_model_nc_cdf<line::Real<128> >(file, k);
10185 if (arith ==
"real:256")
return solve_model_nc_cdf<line::Real<256> >(file, k);
10186 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10188 if (analysis ==
"prob") {
10189 if (arith ==
"double")
return solve_model_nc_prob<double>(file, k);
10190 if (arith ==
"exact")
return solve_model_nc_prob<line::Rational>(file, k);
10191 if (arith ==
"real:16")
return solve_model_nc_prob<line::Real<16> >(file, k);
10192 if (arith ==
"real" || arith ==
"real:32")
10193 return solve_model_nc_prob<line::Real<32> >(file, k);
10194 if (arith ==
"real:64")
return solve_model_nc_prob<line::Real<64> >(file, k);
10195 if (arith ==
"real:128")
return solve_model_nc_prob<line::Real<128> >(file, k);
10196 if (arith ==
"real:256")
return solve_model_nc_prob<line::Real<256> >(file, k);
10197 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10199 if (arith ==
"double")
return solve_model_nc<double>(file, k);
10200 if (arith ==
"exact")
return solve_model_nc<line::Rational>(file, k);
10201 if (arith ==
"real:16")
return solve_model_nc<line::Real<16> >(file, k);
10202 if (arith ==
"real" || arith ==
"real:32")
return solve_model_nc<line::Real<32> >(file, k);
10203 if (arith ==
"real:64")
return solve_model_nc<line::Real<64> >(file, k);
10204 if (arith ==
"real:128")
return solve_model_nc<line::Real<128> >(file, k);
10205 if (arith ==
"real:256")
return solve_model_nc<line::Real<256> >(file, k);
10206 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10214 if (analysis !=
"avg" && analysis !=
"cdf")
10216 "the AG solver ports -a avg (with its views -a node, -a sys, -a chain and "
10217 "-a nodechain) and -a cdf, the inherited exponential fallback: RCAT converges a "
10218 "fixed point over the synchronization rates and reports mean measures, forming no "
10219 "state probability or transient (got '" + analysis +
"')");
10220 if (analysis ==
"cdf") {
10221 if (arith ==
"double")
return solve_model_ag_cdf<double>(file, k);
10222 if (arith ==
"exact")
return solve_model_ag_cdf<line::Rational>(file, k);
10223 if (arith ==
"real:16")
return solve_model_ag_cdf<line::Real<16> >(file, k);
10224 if (arith ==
"real" || arith ==
"real:32")
10225 return solve_model_ag_cdf<line::Real<32> >(file, k);
10226 if (arith ==
"real:64")
return solve_model_ag_cdf<line::Real<64> >(file, k);
10227 if (arith ==
"real:128")
return solve_model_ag_cdf<line::Real<128> >(file, k);
10228 if (arith ==
"real:256")
return solve_model_ag_cdf<line::Real<256> >(file, k);
10229 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10231 if (arith ==
"double")
return solve_model_ag<double>(file, k);
10232 if (arith ==
"exact")
return solve_model_ag<line::Rational>(file, k);
10233 if (arith ==
"real:16")
return solve_model_ag<line::Real<16> >(file, k);
10234 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ag<line::Real<32> >(file, k);
10235 if (arith ==
"real:64")
return solve_model_ag<line::Real<64> >(file, k);
10236 if (arith ==
"real:128")
return solve_model_ag<line::Real<128> >(file, k);
10237 if (arith ==
"real:256")
return solve_model_ag<line::Real<256> >(file, k);
10238 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10241 if (analysis !=
"avg" && analysis !=
"bounds" && analysis !=
"cdf")
10243 "-a cdf, the inherited exponential fallback (got '" +
10245 if (analysis ==
"cdf") {
10246 if (arith ==
"double")
return solve_model_ba_cdf<double>(file, k);
10247 if (arith ==
"exact")
return solve_model_ba_cdf<line::Rational>(file, k);
10248 if (arith ==
"real:16")
return solve_model_ba_cdf<line::Real<16> >(file, k);
10249 if (arith ==
"real" || arith ==
"real:32")
10250 return solve_model_ba_cdf<line::Real<32> >(file, k);
10251 if (arith ==
"real:64")
return solve_model_ba_cdf<line::Real<64> >(file, k);
10252 if (arith ==
"real:128")
return solve_model_ba_cdf<line::Real<128> >(file, k);
10253 if (arith ==
"real:256")
return solve_model_ba_cdf<line::Real<256> >(file, k);
10254 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10256 if (analysis ==
"bounds") {
10257 if (arith ==
"double")
return solve_model_ba_bounds<double>(file, k);
10258 if (arith ==
"exact")
return solve_model_ba_bounds<line::Rational>(file, k);
10259 if (arith ==
"real:16")
return solve_model_ba_bounds<line::Real<16> >(file, k);
10260 if (arith ==
"real" || arith ==
"real:32")
10261 return solve_model_ba_bounds<line::Real<32> >(file, k);
10262 if (arith ==
"real:64")
return solve_model_ba_bounds<line::Real<64> >(file, k);
10263 if (arith ==
"real:128")
return solve_model_ba_bounds<line::Real<128> >(file, k);
10264 if (arith ==
"real:256")
return solve_model_ba_bounds<line::Real<256> >(file, k);
10265 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10267 if (arith ==
"double")
return solve_model_ba<double>(file, k);
10268 if (arith ==
"exact")
return solve_model_ba<line::Rational>(file, k);
10269 if (arith ==
"real:16")
return solve_model_ba<line::Real<16> >(file, k);
10270 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ba<line::Real<32> >(file, k);
10271 if (arith ==
"real:64")
return solve_model_ba<line::Real<64> >(file, k);
10272 if (arith ==
"real:128")
return solve_model_ba<line::Real<128> >(file, k);
10273 if (arith ==
"real:256")
return solve_model_ba<line::Real<256> >(file, k);
10274 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10277 if (analysis !=
"avg" && analysis !=
"cdf")
10279 "SolverLQNS on a Network reports -a avg and -a cdf, the inherited exponential "
10281 "qnsolver returns one chain-level table of means and computes no state "
10282 "probability and no transient (got '" + analysis +
"')");
10286 if (arith !=
"double")
10288 "SolverLQNS reads its results back as the decimal text an external binary printed, "
10289 "which is double at best; --arith " + arith +
10290 " would report a precision the tool never produced");
10291 if (analysis ==
"cdf")
return solve_model_lqns_network_cdf(file, k);
10292 return solve_model_lqns_network<double>(file, k);
10294 if (s ==
"fluid" || s ==
"fld") {
10295 if (analysis !=
"avg" && analysis !=
"odes" && analysis !=
"var" &&
10296 analysis !=
"tranvar" && analysis !=
"jacobian" && analysis !=
"tran" &&
10297 analysis !=
"prob" && analysis !=
"cdf" && analysis !=
"aoi" &&
10298 analysis !=
"statevec")
10300 "the fluid solver ports -a avg, -a tran, -a tranvar, -a prob, -a cdf, -a aoi, "
10301 "-a odes, -a statevec, -a var and -a jacobian (got '" + analysis +
"')");
10304 if (arith !=
"double")
10306 "the fluid solver integrates its drift with LSODA, which is double precision by "
10307 "construction; rerun with --arith double (got '" + arith +
"')");
10308 if (analysis ==
"odes")
return solve_model_fluid_odes<double>(file, k);
10309 if (analysis ==
"statevec")
return solve_model_fluid_statevec<double>(file, k);
10310 if (analysis ==
"jacobian")
return solve_model_fluid_jacobian<double>(file, k);
10311 if (analysis ==
"var")
return solve_model_fluid_var<double>(file, k);
10312 if (analysis ==
"tranvar")
return solve_model_fluid_tranvar<double>(file, k);
10313 if (analysis ==
"tran")
return solve_model_fluid_tran<double>(file, k);
10314 if (analysis ==
"prob")
return solve_model_fluid_prob<double>(file, k);
10315 if (analysis ==
"cdf")
return solve_model_fluid_cdf<double>(file, k);
10316 if (analysis ==
"aoi")
return solve_model_fluid_aoi<double>(file, k);
10317 return solve_model_fluid<double>(file, k);
10319 if (analysis ==
"prob") {
10320 if (arith ==
"double")
return solve_model_prob<double>(file, k);
10321 if (arith ==
"exact")
return solve_model_prob<line::Rational>(file, k);
10322 if (arith ==
"real:16")
return solve_model_prob<line::Real<16> >(file, k);
10323 if (arith ==
"real" || arith ==
"real:32")
return solve_model_prob<line::Real<32> >(file, k);
10324 if (arith ==
"real:64")
return solve_model_prob<line::Real<64> >(file, k);
10325 if (arith ==
"real:128")
return solve_model_prob<line::Real<128> >(file, k);
10326 if (arith ==
"real:256")
return solve_model_prob<line::Real<256> >(file, k);
10327 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10329 if (analysis ==
"marg") {
10330 if (arith ==
"double")
return solve_model_marg<double>(file, k);
10331 if (arith ==
"exact")
return solve_model_marg<line::Rational>(file, k);
10332 if (arith ==
"real:16")
return solve_model_marg<line::Real<16> >(file, k);
10333 if (arith ==
"real" || arith ==
"real:32")
return solve_model_marg<line::Real<32> >(file, k);
10334 if (arith ==
"real:64")
return solve_model_marg<line::Real<64> >(file, k);
10335 if (arith ==
"real:128")
return solve_model_marg<line::Real<128> >(file, k);
10336 if (arith ==
"real:256")
return solve_model_marg<line::Real<256> >(file, k);
10337 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10339 if (analysis ==
"normconst") {
10340 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
10341 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
10342 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
10343 if (arith ==
"real" || arith ==
"real:32")
10344 return solve_model_normconst<line::Real<32> >(file, k, s);
10345 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
10346 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
10347 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
10348 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10350 if (analysis ==
"cdf") {
10353 if (arith ==
"double")
return solve_model_mva_cdf<double>(file, k);
10354 if (arith ==
"exact")
return solve_model_mva_cdf<line::Rational>(file, k);
10355 if (arith ==
"real:16")
return solve_model_mva_cdf<line::Real<16> >(file, k);
10356 if (arith ==
"real" || arith ==
"real:32")
10357 return solve_model_mva_cdf<line::Real<32> >(file, k);
10358 if (arith ==
"real:64")
return solve_model_mva_cdf<line::Real<64> >(file, k);
10359 if (arith ==
"real:128")
return solve_model_mva_cdf<line::Real<128> >(file, k);
10360 if (arith ==
"real:256")
return solve_model_mva_cdf<line::Real<256> >(file, k);
10361 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
10363 if (analysis !=
"avg")
10365 "the model-solving path ports -a avg, -a node, -a prob, -a marg, -a normconst and "
10373 if (arith ==
"double")
return solve_model_mva<double>(file, k);
10374 if (arith ==
"exact")
return solve_model_mva<line::Rational>(file, k);
10375 if (arith ==
"real:16")
return solve_model_mva<line::Real<16> >(file, k);
10376 if (arith ==
"real" || arith ==
"real:32")
return solve_model_mva<line::Real<32> >(file, k);
10377 if (arith ==
"real:64")
return solve_model_mva<line::Real<64> >(file, k);
10378 if (arith ==
"real:128")
return solve_model_mva<line::Real<128> >(file, k);
10379 if (arith ==
"real:256")
return solve_model_mva<line::Real<256> >(file, k);
10381 "--arith '" + arith +
10382 "' is not a model-solve backend; use double, exact or real:<16|32|64|128|256>");
10392void print_brief_help() {
10394 "LINE solver (C++), version %s\n"
10396 "Usage: line-cli -f <model> [-s <solver>] [-a <analysis>] [-o <format>]\n"
10397 " cat model.json | line-cli\n"
10399 "Common options:\n"
10400 " -f, --file <path> model file: .json (network), .lqnx or .in (layered),\n"
10401 " .jsimg (JMT), .pnml (Petri net); stdin if omitted\n"
10402 " -s, --solver <name> auto (default), mva, nc, ctmc, mam, fluid, ssa,\n"
10403 " ldes, jmt, ag, ba, uq; ln for layered models, env\n"
10404 " for random environments\n"
10405 " -a, --analysis <type> avg (default), node, sys, chain, tran, prob, cdf,\n"
10406 " states, sample, normconst, bounds, ... (comma list)\n"
10407 " -o, --output <fmt> readable (default) | json | jsimg (export the\n"
10408 " model as a JMT simulation document)\n"
10409 " --method <name> algorithm within the chosen solver\n"
10410 " --samples <n> simulation run length (ssa, ldes); default 10000\n"
10411 " --seed <n> random seed; default 23000\n"
10412 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
10413 " the solver console, a running progress log\n"
10414 " --find-solver [m] which solvers and methods can analyze this model,\n"
10415 " optionally only those answering measure <m>\n"
10416 " (avg, tran, cdf, prob, sample, ...); reports and exits\n"
10417 " --find-solver-all [m] the same, keeping the refused pairs and the\n"
10418 " reason each was refused\n"
10419 " -h, --help this message\n"
10420 " --help-all every option, solver by solver\n"
10421 " -V, --version version string\n"
10422 " --install environment check: which optional backends\n"
10423 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
10426 " line-cli -f model.json solve, letting auto pick the solver\n"
10427 " line-cli -f model.json -s mva -a avg mean queue lengths, MVA\n"
10428 " line-cli -f model.lqnx -s ln solve a layered model\n"
10429 " line-cli -f model.json -s ssa --samples 1e6 -o json\n"
10430 " line-cli -f model.json --find-solver what can solve this model\n"
10431 " line-cli -f model.json --find-solver cdf ... and return a passage-time law\n"
10433 "Each solver has flags of its own (tolerances, horizons, engine choices):\n"
10434 "run `line-cli --help-all` for the full reference.\n",
10440 "LINE multiprecision solver (C++), version %s\n"
10442 "Usage: line-cli [OPTIONS]\n"
10443 " cat model.json | line-cli -i json [OPTIONS]\n"
10444 " line-cli model.lqnx [OPTIONS]\n"
10446 "Options (flag-compatible with jline.cli.LineCLI):\n"
10447 " -f, --file <path> model file; stdin when omitted (json only)\n"
10448 " -i, --input <fmt> input format: json | jsim | jsimg | jsimw |\n"
10449 " lqnx | xml | srvn | pnml. Without it a .lqnx or\n"
10450 " .xml path is read as a layered model, a .in\n"
10451 " path (or a .lqn one not opening with '<') as\n"
10452 " a layered model in the LQNS SRVN text, a\n"
10453 " .jsim/.jsimg/.jsimw path as a JMT simulation\n"
10454 " document, a .pnml path as a place/transition\n"
10455 " net (ISO/IEC 15909-2), and anything else as a\n"
10456 " Network model.json. The three jsim spellings\n"
10457 " name ONE format, as they do in JMT\n"
10458 " -o, --output <fmt> output format: readable | json | jsimg\n"
10459 " (| layers, lqnx for layered models).\n"
10460 " jsimg exports the model as a JMT simulation\n"
10461 " document and solves nothing -- the write half\n"
10462 " of -i jsim|jsimg|jsimw, and the same three\n"
10463 " spellings are accepted. --seed, --samples and\n"
10464 " --tspan set the controls its header carries\n"
10465 " json is honoured by EVERY analysis, not only\n"
10466 " -a avg: each answers under a key named after\n"
10467 " its -a, with the arithmetic and the resolved\n"
10468 " method beside it, and every index inside a\n"
10469 " payload is 0-based against the tables' 1-based\n"
10470 " columns (each payload states its indexBase)\n"
10471 " -s, --solver <name> Network: auto, mva, nc, ctmc, mam, ba, ssa, fluid, uq,\n"
10472 " ldes (the SSJ discrete-event engine, run as a\n"
10473 " subprocess on common/ldes or common/ldes.jar),\n"
10474 " jmt (the Java Modelling Tools engine),\n"
10475 " lqns (its qns methods, the external qnsolver\n"
10477 " layered: auto, ln, ln.mva, ln.comom, lqns,\n"
10478 " ldes (the native in-process LN simulator, a\n"
10479 " sample path of the layered model itself and\n"
10480 " not a decomposition into layers; it takes\n"
10481 " --samples and --seed, and NOT the --ldes-*\n"
10482 " family, which configures the subprocess\n"
10483 " engine that answers -s ldes on a Network)\n"
10484 " environment: env (an Environment model.json)\n"
10485 " -a, --analysis <type> analysis: avg, node, sys, chain, nodechain,\n"
10486 " stage, cache, item, fj, prob, marg, cdf, cdf-passt,\n"
10487 " perct-respt, tran, tran-cdf-respt,\n"
10488 " tran-cdf-passt, tranprob, tranreward,\n"
10489 " reward-value, normconst, gen, states, sample,\n"
10490 " reward, sens, first-passt, odes, statevec,\n"
10491 " var, tranvar, busyperiod,\n"
10492 " jacobian, aoi, internals, bounds, posterior,\n"
10493 " interval. A COMMA LIST runs several in order\n"
10494 " (`-a avg,sys`), emitting one -o json envelope\n"
10495 " per analysis rather than one merged object.\n"
10496 " The JAR CLI's own spellings are accepted as\n"
10497 " aliases -- cdf-respt, prob-sys-aggr, tran-avg,\n"
10498 " generator, reward-steady, all -- and collapse\n"
10499 " onto the arm that already answers them whole:\n"
10500 " -a prob reports getProbSys, getProbSysAggr and\n"
10501 " the per-station pair together, -a sample walks\n"
10502 " all four samplers at once, and -a stage IS\n"
10503 " -a avg on a Network (one implicit stage).\n"
10505 " solver serves is stated by its own refusal;\n"
10506 " ssa serves avg, prob and sample (prob and\n"
10507 " sample run the SERIAL engine whatever -m\n"
10508 " said, since the NRM simulates counts rather\n"
10509 " than the state encoding)\n"
10510 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
10511 " the solver console, a running progress log\n"
10512 " of every solver run\n"
10513 " -d, --seed <n> random seed (SSA, nc --method cftp); default\n"
10514 " 23000. -d is the JAR CLI's spelling\n"
10515 " --warmupfrac <f> SSA: leading fraction of the path discarded\n"
10516 " before the means are taken, in [0,1); unset,\n"
10517 " 0.2 on a model with an open class, else 0\n"
10518 " --pstar <p> fluid: exponent of the p-norm smoothing of the\n"
10519 " drift; without it the hard min() is integrated\n"
10520 " --fluid-boundary <m> fluid (Petri dae): reflect (default) reports a\n"
10521 " fluid place level held at a bound as the bound;\n"
10522 " diffusion as a reflected Brownian motion's mean\n"
10523 " --nonmkv <method> ctmc, mam, fluid, ag: bernstein (default) fits a\n"
10524 " phase-type to a non-Markovian law; none skips it\n"
10525 " --busyperiod <n,..> -a busyperiod: the orders wanted; default 1\n"
10526 " --busyperiod-subnet <i,..>\n"
10527 " -a busyperiod: the 1-based stations forming the\n"
10528 " subnetwork. Required -- a busy period is defined\n"
10529 " for a NAMED set and no default can choose one\n"
10530 " --method <name> algorithm within the solver\n"
10531 " --samples <n> simulation run length (SSA); default 10000.\n"
10532 " Accepts 1e6 as well as 1000000. A simulation\n"
10533 " figure is only a measurement WITH this number,\n"
10534 " which is why the SSA banner reports it back.\n"
10535 " Also the draw count of nc --method cftp,\n"
10536 " default 1e5, where the draw is the answer.\n"
10537 " --mdd-tol <x> level-iteration tolerance of ctmc --method mdd;\n"
10538 " default 1e-12. NOT --tol: that iteration is an\n"
10539 " inner solve whose fixed point is checked\n"
10540 " against the population invariant at 1e-6, so a\n"
10541 " solver-sized tolerance stops short of it\n"
10542 " --mdd-maxiter <n> coupled sweeps before ctmc --method mdd is\n"
10543 " declared non-convergent; default 500\n"
10544 " --level <n> hierarchy level of the ba pbh/bjbh/cbh/sib\n"
10545 " families; default 2\n"
10546 " --qrf-params <j> JSON (inline or a path) with the QRF blocking\n"
10547 " tables of -s ba --method qrf.bas|qrf.rsrd:\n"
10548 " f, MR, BB, MM, MM1, ZZ and optionally F, the\n"
10549 " fields sn_to_qrf_params assembles. ZM is\n"
10550 " derived from ZZ. There is no default: assuming\n"
10551 " no blocking puts the bound ~31x farther from\n"
10552 " exact, so its absence is refused\n"
10553 " --qrf-alpha <j> JSON (nstations x N) load-dependent scaling of\n"
10554 " the ba qrf.mmi.ld, qrf.mmi.linear and qrf.rsrd\n"
10555 " arms; default all ones\n"
10556 " --tol <x> convergence tolerance (mva, nc, mam, ag, fluid)\n"
10557 " --iter_tol <x> outer-loop tolerance (mva, nc, fluid)\n"
10558 " --iter_max <n> iteration cap (mva, nc, mam, ag, fluid)\n"
10559 " --max-states <n> truncation level of an OPEN agent's queue-length\n"
10560 " dimension (ag), options.config.maxStates;\n"
10561 " default 100. A closed class is bounded by its\n"
10562 " own population instead, and --method inapinf\n"
10563 " ignores the level and solves the open agents on\n"
10564 " the infinite state space\n"
10565 " --fork-join <arm> which fork-join transform the mean-value fixed\n"
10566 " point takes (mva, nc): default|mmt|fjt is the\n"
10567 " MMT transform, ht|heidelberger-trivedi the\n"
10568 " Heidelberger-Trivedi one, which is CLOSED\n"
10569 " models only and a different answer to the same\n"
10570 " model rather than a faster route to one\n"
10571 " --cutoff <n|matrix> open jobs per class in the CTMC state space;\n"
10572 " a matrix is per (station,class), '1,1,0;3,3,0;0,0,3'\n"
10573 " (ctmc); without it the reference's\n"
10574 " ceil(6000^(1/(M*K))) is used and reported.\n"
10575 " Also the level truncation of mam -a prob,\n"
10576 " whose open queue has no bound of its own\n"
10577 " --fj-accuracy <n> FJ_codes truncation C of the queue-length\n"
10578 " difference between the two fork-join\n"
10579 " branches (mam, homogeneous fork-join);\n"
10580 " default 100, larger is more accurate\n"
10581 " --fj-tmode <mode> how that approximation solves for its T\n"
10582 " matrix: NARE (default) or Sylvester\n"
10583 " --sync-phases <n> synchronous phase expansion of mam\n"
10584 " --method sync.faster|sync.slower; 0 (default)\n"
10585 " solves without expansion, n >= 1 needs a\n"
10586 " deterministic service and tightens both bounds\n"
10587 " --mapmsg-quantization <n>\n"
10588 " patience regimes of the mam MAP/M/s+G reneging\n"
10589 " analyzer; default 81, at least 2\n"
10590 " --timescale <mode> auto (default), discrete or continuous: how\n"
10591 " -s mam reads the time scale. auto lets the\n"
10592 " distributions decide; discrete raises rather\n"
10593 " than solve a model that mixes lattice and\n"
10594 " non-lattice laws. The slot is --slotlength\n"
10595 " --tspan <t0>:<t1> transient horizon (ctmc -a tranprob,\n"
10596 " mam -a tran, fluid); a bare <t1> starts at 0.\n"
10597 " For the CTMC and MAM there is no default:\n"
10598 " pi(t) on an unstated horizon is not a\n"
10599 " quantity. For the fluid solver it bounds the\n"
10600 " integration, and is what -s fluid --method kp\n"
10601 " reports its covariance AT\n"
10602 " -n, --node <n> 1-based stateful node a state query is\n"
10603 " labelled by (ctmc -a tranprob, -a sample and\n"
10604 " ssa -a sample), the\n"
10605 " queue mam -a prob reports, or the station\n"
10606 " mva -a marg reports; without it the\n"
10607 " whole network is reported (the MAM queries\n"
10608 " take the model's only Queue)\n"
10609 " -c, --class <r> 1-based job class of mva -a marg; without it\n"
10610 " every class is reported\n"
10611 " NOTE ON THE INDEX BASE: -n and -c are 1-BASED\n"
10612 " here, as every station index this CLI takes\n"
10613 " is, and 0-BASED in jline.cli.LineCLI, which\n"
10614 " indexes as Java does. The short spellings are\n"
10615 " accepted so one command line parses in both,\n"
10616 " but the SAME number names a different node --\n"
10617 " the two bridges (cpp_dispatch, jar_dispatch)\n"
10618 " each convert for their own CLI\n"
10619 " --marg-states <ns> comma-separated job counts the mva -a marg\n"
10620 " curve is evaluated at, the reference's\n"
10621 " state_m; without it each law takes its own\n"
10622 " default range (0..N_r closed, mean + 5 sigma\n"
10623 " Poisson, the 1e-10 tail geometric)\n"
10624 " --notation <form> scalar (default) | matrix, the form the ODE\n"
10625 " export writes (fluid -a odes only)\n"
10626 " --symbolic <b> computer-algebra backend of fluid -a\n"
10627 " jacobian: auto (default, searches for a\n"
10628 " line-sage-rest service), a URL, an image\n"
10629 " name, or none to differentiate locally\n"
10630 " --equilibria also ask the backend for the solutions of\n"
10631 " f(x) = 0 (fluid -a jacobian). Needs a\n"
10632 " backend: solving is not differentiating\n"
10633 " --cdf-algorithm <a> exact (default, pfqn_stdf) | rd\n"
10634 " (pfqn_stdf_heur), how the sojourn law is\n"
10635 " inverted (nc -a cdf only)\n"
10636 " --perm-engine <e> exact (default, Ryser) | spm | bethe | heur |\n"
10637 " huberlaw | adapart, the permanent estimator\n"
10638 " of nc -a sysmarg. The five approximations\n"
10639 " refuse a demand matrix with a zero entry;\n"
10640 " spm is the saddle point, whose cost does not\n"
10641 " grow with the population\n"
10642 " --env-init <p,...> initial probability of each environment stage,\n"
10643 " options.config.env_init of -s env --method\n"
10644 " occupation; default the first stage. --tspan\n"
10645 " <t1> sets its horizon, unbounded without it\n"
10646 " --tran-points <n> points on the uniform transient grid the ENV\n"
10647 " mean-field coupling sums its stage exit\n"
10648 " metrics over (env only); default 1001\n"
10649 " --state <n,...> the ENCODED state row of the node named by\n"
10650 " --node that -a prob asks about, i.e.\n"
10651 " getProb(node, state)'s second argument; without\n"
10652 " it the query is about the model's default\n"
10654 " --events <n> length of ONE sampled trajectory (-a sample);\n"
10655 " default 1000. NOT --samples, which is a\n"
10656 " solver's run length\n"
10657 " --timestep <dt> fixed output step of a transient CTMC solve\n"
10658 " (-a tranprob, -a tranreward), options.timestep\n"
10659 " of ctmc_transient.m; without it the grid is the\n"
10660 " integrator's own adaptive one. It changes WHERE\n"
10661 " the solution is reported, not how it is\n"
10662 " computed: the grid points are read off the same\n"
10664 " --transient-method <m> ode (default) or fau, how the CTMC forward\n"
10665 " equation is advanced over the output grid\n"
10666 " --fau-epsilon <e> fau: probability mass the grid may discard\n"
10667 " --fau-delta <d> fau: occupancy below which a state is dropped\n"
10668 " --rate-sched <j> -s ctmc -a tran: time-varying rates, a JSON array\n"
10669 " (inline or a file) of {station, class, tgrid,\n"
10670 " rates[, nominal]}; station and class by name or\n"
10671 " 1-based index. The rate of that pair follows the\n"
10672 " piecewise-linear schedule, held at its end values\n"
10673 " outside tgrid (options.config.rate_sched)\n"
10674 " --ctmc-tv-ngrid <n> grid size of the --rate-sched propagator (100)\n"
10675 " --percentiles <p,..> levels getPerctRespT is read at (-s mam),\n"
10676 " as fractions (0.9) or percents (90); default\n"
10677 " 0.50,0.90,0.95,0.99, the reference's pers_stored\n"
10678 " --reward-name <nm> which declared reward -a reward-value returns\n"
10679 " the value function of. REQUIRED there and never\n"
10680 " defaulted: two rewards have different value\n"
10681 " functions, and picking one would mislabel it\n"
10682 " --map-env <mode> auto (the default) | off, whether a model whose ONLY\n"
10683 " unsupported features are MAP/MMPP2/MMAP/MPH is\n"
10684 " solved through a random-environment image of the\n"
10685 " modulating chain instead of being refused. Not a\n"
10686 " -s env knob: it applies to mva, nc and fluid,\n"
10687 " which is where the refusal it replaces comes\n"
10688 " from. off restores that plain refusal\n"
10689 " --map-env-method <m> auto (default) | dec | avg | meanfield, the\n"
10690 " recombination the image is solved with. auto\n"
10691 " picks from the environment timescale; meanfield\n"
10692 " needs a stage backend and is refused by name\n"
10693 " where there is none\n"
10694 " --map-env-maxstages <n> cap on the stages map2renv draws from the\n"
10695 " modulating chain; 0, the default, takes every\n"
10696 " phase. Beyond the JAR, which has no such cap\n"
10697 " -p, --port <n> run as a solve SERVER on this port, speaking\n"
10698 " LineWebSocketServer's protocol: one WebSocket\n"
10699 " text message per connection, its first line the\n"
10700 " comma-separated argument list and its remainder\n"
10701 " the model document; the CLI's output comes back\n"
10702 " as one text message. Plaintext, one connection\n"
10703 " at a time, and -f is refused beside it\n"
10704 " -m, --maxreq <n> quit after serving n requests; without it the\n"
10705 " server runs until interrupted\n"
10706 " -h, --help the short message: the flags a first run needs\n"
10707 " --help-all this message, every option solver by solver\n"
10708 " -V, --version version string\n"
10709 " --install environment check: report which optional backends\n"
10710 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
10712 "Layered models (-i lqnx|xml) additionally take:\n"
10713 " --layer-solver <s> solver run in each layer: mva (default)|nc|\n"
10714 " fluid|ssa|mam|ctmc|ldes|jmt, the C++ spelling of the\n"
10716 " factory argument: LN(model, @(m) MVA(m))\n"
10717 " against LN(model, @(m) Fluid(m)). They converge\n"
10718 " to DIFFERENT fixed points, not to the same one\n"
10719 " by different routes, because each layer's\n"
10720 " results feed the next outer iteration's demands.\n"
10721 " fluid is double only and refuses a layer with a\n"
10722 " fork; ssa, ldes and jmt are NOISY, so the outer\n"
10723 " loop switches to the Robbins-Monro / Polyak-Ruppert\n"
10724 " controller, and --samples/--seed set their run\n"
10725 " length and BASE seed (layer e of iteration it runs\n"
10726 " at seed+(it-1)*nlayers+e). ldes is the native\n"
10727 " engine in process; jmt runs JSIM per layer\n"
10728 " --method <name> the LN method: default | srvn | srvn.ph |\n"
10729 " srvn.cs | flat | flat.cs | flat.ph | moment3 |\n"
10730 " mw.upper | mw.lower. srvn*/flat* choose the\n"
10731 " layering (one submodel per server, or one for\n"
10732 " all) and the encoding (.cs routing, .ph composed\n"
10733 " phase-type law); default is srvn. moment3 fits an APH to each layer's\n"
10734 " response-time CDF and convolves along the entry,\n"
10735 " which is what makes -a cdf possible; mw.*\n"
10736 " reports Majumdar-Woodside box BOUNDS on\n"
10737 " throughput and processor utilization and solves\n"
10738 " no layer at all (every other metric is NaN)\n"
10739 " --ln-transient <m> coupled (default) | decoupled, how -a tran\n"
10740 " couples the layers. decoupled freezes the\n"
10741 " inter-layer demands at the fixed point; coupled\n"
10742 " relaxes time-varying demands through the fluid\n"
10743 " rate schedule until the trajectories settle\n"
10744 " --ln-transient-channels <c> both (default) | thinkt | callservt,\n"
10745 " which inter-layer coupling the relaxation\n"
10746 " injects, for isolating one channel's share\n"
10747 " --sens-method <m> auto (default) | exact | fd, the branch each\n"
10748 " LAYER's sensitivity table takes (-a sens);\n"
10749 " the same three under -s nc -a sens\n"
10750 " --sens-scheme <s> forward (default) | central, the difference\n"
10751 " quotient of the fd branch\n"
10752 " --sens-step <h> relative rate perturbation of the fd branch,\n"
10753 " in (0,1); default 1e-4, or 1e-2 for ssa layers\n"
10754 " --no-interlocking disable the interlocking correction\n"
10755 " --interlock-method <m> ilrate (default) | refpath | none, how\n"
10756 " the interlock is tracked; refpath merges the\n"
10757 " callers that are one reference task's\n"
10758 " customers into one chain (srvn.cs only, it\n"
10759 " falls back to ilrate elsewhere, with a warning)\n"
10760 " --interlock-maxpaths <n> refpath refuses a layer carrying more\n"
10761 " reference routes into it; default 32\n"
10762 " --interlock-refpath-scope <s> merging (default) | all, whether\n"
10763 " refpath also rewrites a lone-caller layer\n"
10764 " --method nlp (alias qdamva, qd-amva) build NO layers: root\n"
10765 " the QD-AMVA laws of the whole model as one\n"
10766 " nonlinear program (-a avg only)\n"
10767 " --lld <STATION=EXPR> a smooth rate multiplier in n for a\n"
10768 " processor or task, read by nlp; repeatable\n"
10769 " --nlp-alpha <a> softmin sharpness for min(n, m); default 20\n"
10770 " --nlp-warm <k> warm sweeps the Newton ladder starts from; 0\n"
10771 " --nlp-maxiter <k> budget of the fallback descent; default 3000\n"
10772 " --nlp-fork <f> refuse (default) | exp, how nlp prices an\n"
10773 " AND-fork: E[max] of exponential branches\n"
10774 " --warmstart <w> nlp | mol, seed a LAYERED method's fixed\n"
10775 " point from the program or the Method of\n"
10776 " Layers; not with srvn.ph/flat.ph or nlp\n"
10777 " --mol-iter-max <k>, --mol-iter-tol <t>, --mol-relax-factor <w>\n"
10778 " lqn_mol's settings for --warmstart mol;\n"
10779 " defaults 200, 1e-6, 0.5\n"
10780 " --repeat <k> re-solve k times, report the best wall clock\n"
10781 " -o layers dump every layer's stations, classes, rates\n"
10782 " and routing instead of the AvgTable\n"
10783 " -a takes avg (getAvgTable), tran (getTranAvg, needs --tspan and fluid\n"
10784 " layers), sens (getSensitivityTable) and cdf (getCdfRespT, moment3).\n"
10785 " --iter_max and --iter_tol set the outer LN loop; --tol does not apply,\n"
10786 " and neither does any Network solver token.\n"
10788 "The external LQNS binary (-s lqns) additionally takes:\n"
10789 " --method <name> default | lqns | srvn | exactmva |\n"
10790 " srvn.exactmva | sim | lqsim | lqns.default.\n"
10791 " sim and lqsim run lqsim, the SIMULATOR;\n"
10792 " lqns.default is lqns with no pragma at all,\n"
10793 " which is a different fixed point and not a\n"
10794 " synonym for default\n"
10795 " --multiserver <p> conway|rolia|zhou|suri|reiser|schmidt|default\n"
10796 " (= rolia), the -Pmultiserver= pragma. Not\n"
10797 " passed to lqsim, which has no MVA to configure\n"
10798 " --samples <n> lqsim run length (-A); default 10000\n"
10799 " --timeout <s> kill the child after s seconds; without it the\n"
10801 " --keep keep the working directory with model.lqnx and\n"
10802 " model.lqxo instead of removing it\n"
10803 " --verbose echo the command line and the binary's output\n"
10804 " --remote[-url <u>] solve on a host running lqns-rest instead of\n"
10805 " locally; -url implies --remote. LINE ships no\n"
10806 " LQNS binary, so this is the other way to reach\n"
10808 " -a takes avg only: lqns computes no transient, no sensitivity and no\n"
10809 " response-time distribution. QLen is the element utilization, Util its\n"
10810 " processor utilization per server, RespT its phase-1 service time;\n"
10811 " ResidT and ArvR print NaN because lqns reports neither.\n"
10813 "The external qnsolver binary (-s lqns on a Network) additionally takes:\n"
10814 " --method <name> default | qns (both = rolia) | qns.conway |\n"
10815 " qns.rolia | qns.zhou | qns.reiser | qns.suri |\n"
10816 " qns.schmidt. The multiserver approximation,\n"
10817 " passed as qnsolver -m and only\n"
10818 " when the model HAS a multiserver station. suri\n"
10819 " and schmidt are lqns approximations: they reach\n"
10820 " the tool only on the non-product-form closed\n"
10821 " branch below, and a multiserver model on the\n"
10822 " qnsolver branch refuses them by name\n"
10823 " --multiserver <p> the same choice under its config spelling;\n"
10824 " --method wins when it names one\n"
10825 " --timeout <s> kill the child after s seconds; without it the\n"
10827 " --keep keep the working directory with model.jmva and\n"
10828 " result.jmva instead of removing it\n"
10829 " -a takes avg only. The model is marshalled to the JMVA interchange\n"
10830 " format at CHAIN level and the chain results are de-aggregated back to\n"
10831 " classes, so only Queue, Delay and Source stations are expressible; any\n"
10832 " other station is refused rather than dropped. A closed model that is\n"
10833 " NOT product-form is converted with QN2LQN and delegated to lqns, as the\n"
10834 " reference does, so it also needs the lqns binary; class priorities are\n"
10835 " outside that conversion and are refused. --arith is double only, since\n"
10836 " the results arrive as the text an external binary printed.\n"
10838 "The Java Modelling Tools wrapper (-s jmt) additionally takes:\n"
10839 " --jmt-replications <n> independent JSIM runs the transient ensemble\n"
10840 " of -a tran and -a tranprob averages over\n"
10841 " (options.config.replications, default 10),\n"
10842 " replication k seeded seed+k-1. A single path\n"
10843 " is NOT E[N](t); --method jsim is one run\n"
10845 "Discrete-event simulation (-s ldes) additionally takes:\n"
10846 " --ldes-tranfilter <f> warmup filter: mser5 (default), fixed, none\n"
10847 " --ldes-warmupfrac <x> fraction the fixed filter discards (0.2)\n"
10848 " --ldes-cimethod <m> CI estimator: obm (default), bm, spectral,\n"
10850 " --ldes-cnvgon stop on relative precision instead of on\n"
10851 " the --samples budget\n"
10852 " --ldes-cnvgtol <x> that precision target (0.05); implies\n"
10854 " --slotted run the analytical solver on a discrete\n"
10855 " (slotted) time scale; SolverNC routes to the\n"
10856 " discrete-time product form and refuses a model\n"
10858 " --slotlength <x> the slot in model time units; implies --slotted\n"
10859 " --ldes-slotted run on a discrete (slotted) time scale; a\n"
10860 " sample off the lattice is an error, never\n"
10862 " --ldes-slotlength <x> the slot (1.0); implies --ldes-slotted\n"
10863 " --ldes-replications <n> independent runs, averaged. A single path\n"
10864 " is NOT E[N](t): -a tran over an ensemble\n"
10865 " mean needs this\n"
10866 " --ldes-numthreads <n> workers for those replications\n"
10867 " --ldes-maxtime <s> wall-clock budget; the engine stops early\n"
10868 " and reports stopping=max_time\n"
10869 " --ldes-initsol <v,..> warm-start placement, station-major\n"
10870 " [st0_cl0, st0_cl1, ...]. Add --ldes-tranfilter\n"
10871 " fixed --ldes-warmupfrac 0 to reproduce\n"
10872 " initFromSolver, which assumes the placement\n"
10873 " is already a steady state\n"
10874 " --ldes-rest-url <u> solve on an LDES REST server instead of a\n"
10875 " local binary; same wire format, same numbers\n"
10876 " The model.json is forwarded to the engine BYTE FOR BYTE, so a model\n"
10877 " this port cannot itself parse (a cache with retrieval, an SPN, a\n"
10878 " polling server) is simulated exactly as the MATLAB and Python clients\n"
10879 " simulate it. -a avg, tran, cdf (the empirical response-time law),\n"
10880 " sample, reward and prob (the histogram's residence time at the declared\n"
10881 " per-class counts; --node with --state overrides one station) are\n"
10882 " served. --arith is double only.\n"
10884 "Uncertainty quantification (-s uq) additionally takes:\n"
10885 " --uq-solver <s> the engine run at each design point: mva, nc,\n"
10886 " mam, ba, ctmc, fluid or ssa. REQUIRED: UQ\n"
10887 " computes nothing itself, and defaulting it\n"
10888 " would attribute the numbers to an engine the\n"
10889 " caller never chose\n"
10890 " A model whose service or arrival process is a Prior is a FAMILY of\n"
10891 " models. UQ discretizes each Prior, solves the tensor product of the\n"
10892 " alternatives, and reports the prior-weighted expectation. Under -s uq\n"
10893 " three flags describe the DESIGN and not the engine: --method is\n"
10894 " quadrature (default, and the alias of discrete) or montecarlo,\n"
10895 " --samples the nodes per continuous Prior (11), --seed the Monte Carlo\n"
10896 " stream. The stage solver therefore keeps its own sample count and its\n"
10897 " own seed; --tol, --iter_tol, --iter_max and --cutoff pass through to\n"
10898 " it, since UQ has no convergence of its own. -a posterior prints every\n"
10899 " design point, its weight and the means it substituted, which is what\n"
10900 " says whether the expectation averaged two nearby models or two very\n"
10901 " different ones. Every other solver REFUSES a model carrying a Prior\n"
10902 " rather than lowering it to its mixture moments.\n"
10903 " -a interval answers the OTHER epistemic question, in which a\n"
10904 " parameter is bounded but not distributed: it drops the weights and\n"
10905 " keeps the endpoints. On a single-class closed model of LI\n"
10906 " single-server queues and delays it is the EXACT hull of MVA over the\n"
10907 " demand box (2*(m+2) MVA calls, no design solved at all); otherwise it\n"
10908 " falls back to the range over the solved design points, which for a\n"
10909 " continuous Prior lies strictly inside the true range. The table says\n"
10910 " which, and the fallback warns on stderr: a range that is not an\n"
10911 " enclosure must not read like one.\n"
10913 "Random environments (-s env) read an Environment model.json, whose\n"
10914 " stages each hold a Network and whose transitions carry the stage\n"
10915 " holding times. The stages are solved TRANSIENTLY and coupled: each\n"
10916 " stage starts from the queue lengths the previous one left, and the\n"
10917 " reported means are the per-stage sojourn averages blended by the\n"
10918 " environment probabilities. --method selects the coupling: meanfield\n"
10919 " (default, the reference's, carries the marginal means across a\n"
10920 " switch) or statevec|blend (carries the whole joint distribution).\n"
10921 " meanfield solves each stage with the fluid transient and is double\n"
10922 " only; statevec uniformizes a CTMC and takes the whole --arith ladder.\n"
10923 " --method avg|dec asks instead for a closed-form limit, which carries\n"
10924 " nothing across a switch and iterates nothing: avg solves ONE model\n"
10925 " whose modulated rates are their probEnv-weighted averages (exact as\n"
10926 " the environment gets fast), dec solves each stage in steady state and\n"
10927 " blends by probEnv (exact as it gets slow). A model with an\n"
10928 " environment-declared node breakdown is read from the nodeFailures\n"
10929 " block, in the expanded or the one-stage macro form.\n"
10930 " --tspan bounds the stage horizon and --tran-points its grid; --iter_tol\n"
10931 " and --iter_max drive the fixed point. RespT and ArvR print as nan\n"
10932 " because ENV computes neither -- the reference returns them as NaN too,\n"
10933 " and ResidT carries QLen/Tput.\n"
10935 "Additions specific to this port:\n"
10936 " --arith <mode> double (default) | exact | real:<digits>\n"
10937 " --list-api list the API functions ported so far\n"
10938 " --generate <spec> draw a random model and print it, solving\n"
10939 " nothing. <spec> is a JSON object inline, a\n"
10940 " path to one, or `-` for standard input.\n"
10941 " {\"kind\":\"network\"} draws a flat model and\n"
10942 " prints the line-model JSON document; its keys\n"
10943 " are seed, name, queues, delays, openClasses,\n"
10944 " closedClasses, schedStrat, routingStrat,\n"
10945 " distribution, cclassJobLoad,\n"
10946 " varyingServiceRates, multiServerQueues,\n"
10947 " randomCSNodes, multiChainCS and topology\n"
10948 " (rand|cyclic). A `delays` of -1, the default,\n"
10949 " lets the generator split the stations itself.\n"
10950 " {\"kind\":\"layered\"} draws an LQN and prints\n"
10951 " the .lqnx document; its keys are seed, name,\n"
10952 " clients, levels, tasks, processors,\n"
10953 " populationRange, thinkTimeRange,\n"
10954 " taskMultiRange, procMultiRange,\n"
10955 " hostDemandRange, synchCallRange (each a\n"
10956 " [lo,hi] pair), taskInfProbability and\n"
10957 " procInfProbability. Both draw from\n"
10958 " java.util.Random at `seed`, so a seeded spec\n"
10959 " reproduces; --arith is not read, since a draw\n"
10960 " is a double whatever the model is solved at.\n"
10961 " --api <name> invoke one API function directly\n"
10962 " --args <path> JSON arguments for --api; stdin when omitted\n"
10964 "Solvers and what they honour. Every model-solving solver reads -a avg,\n"
10965 "-f/-i and --method; nothing else is wired, so tolerances, iteration\n"
10966 "caps, seeds and sample counts keep their SolverOptions defaults rather\n"
10967 "than being invented here. mva and nc additionally read -a prob -- mva\n"
10968 "fits a binomial to its own means, nc returns the exact product-form\n"
10969 "probability, so the two disagree by construction. mva also reads -a\n"
10970 "marg, @SolverMVA's getProbMarg: P(n jobs of class r at station i) for\n"
10971 "every (station, class) pair, narrowed by --node / --class and evaluated\n"
10972 "at --marg-states. A closed class takes the Schmidt binomial fitted to\n"
10973 "Q(i,r); an open one takes the station's exact BCMP marginal (Poisson at\n"
10974 "an infinite server, multinomial-geometric at a queue). Both solvers read\n"
10975 "-a normconst, getProbNormConstAggr: nc reports the constant its solve\n"
10976 "already formed, mva RE-ENTERS its analyzer at method='exact', since only\n"
10977 "the exact recursion carries a G -- a model whose branch forms none, an\n"
10978 "open or mixed one above all, reports nan, as the reference does. nc also\n"
10980 "@SolverNC's getCdfRespT: the whole response-time law per (station,\n"
10981 "class) on one logarithmic grid, FCFS stations only, with\n"
10982 "--cdf-algorithm exact (pfqn_stdf, the default) or rd (pfqn_stdf_heur).\n"
10983 "nc reads -a sens as well, @NetworkSolver's getSensitivityTable: the\n"
10984 "derivative of each row's means with respect to its own service rate,\n"
10985 "selected with --sens-method / --sens-scheme / --sens-step. NC is one of\n"
10986 "the two engines whose exact branch differentiates the product-form\n"
10987 "recursion analytically, so auto takes it wherever the model is in its\n"
10988 "scope (single-server queues plus delays, not mixed) and falls back to\n"
10989 "finite differences elsewhere. NOT the -s ctmc -a sens analysis, which\n"
10990 "is getSensitivityRanking, a ranking of rate perturbations and not a\n"
10991 "table of derivatives.\n"
10992 "-a node is @NetworkSolver's getAvgNodeTable and is served by mva, nc,\n"
10993 "mam, ba and ctmc, the model solvers whose runner returns the station\n"
10994 "AvgResult it is built from. It is a DIFFERENT INDEX SPACE from -a avg,\n"
10995 "not a relabelling: the AvgTable has one row per STATION, so a\n"
10996 "ClassSwitch, Router, Fork, Join or Sink never appears in it, yet jobs\n"
10997 "flow through all of them. QLen, Util, RespT and ResidT are the station\n"
10998 "numbers scattered to their node indices and zero elsewhere -- a node\n"
10999 "that is not a station holds no jobs -- while ArvR and Tput are\n"
11000 "recomputed for every node by sn_get_node_arvr_from_tput and\n"
11001 "sn_get_node_tput_from_tput. The reference's finite-capacity-region\n"
11002 "pseudo-node rows are NOT emitted: this port's AvgResult carries no\n"
11003 "per-region queue length or utilization to fill them with.\n"
11005 "auto picks the engine\n"
11006 "with chooseSolverHeur and prints the name it picked; a branch selecting\n"
11007 "JMT or LDES refuses by name rather than substituting another. ctmc reads\n"
11008 "--cutoff and ports -a avg, prob, gen, states, tranprob, sample, reward,\n"
11009 "cdf, first-passt and sens, which are @SolverCTMC's\n"
11010 "getProbSys/getProbSysAggr and the per-station getProb/getProbAggr,\n"
11011 "getInfGen, getStateSpace, getTranProbSysAggr, sampleSys, getAvgReward,\n"
11012 "getCdfRespT, getCdfFirstPassT (state sets via --passage-from and\n"
11013 "--passage-into, as 1-based space rows '3,5' or state rows '0,2;1,1'),\n"
11014 "first-passt-moments (-a firstpasstmom, the same two sets plus\n"
11015 "--passage-orders: exact moments by one linear solve per order, so a\n"
11016 "variance or a skewness costs no truncated curve; both take --passage-taboo,\n"
11017 "a TABOO set C in the same syntax: entering it first loses the passage, the\n"
11018 "curve becomes defective and the moments conditional on B before C, and\n"
11019 "firstpasst also takes --passage-tset 't1,t2,...,inf' for its grid)\n"
11020 "and getSensitivityRanking. Its --method also takes mdd, which holds the\n"
11021 "reachable set in a decision diagram and solves K coupled level-CTMCs\n"
11022 "instead of the |S|-state generator; it serves -a avg only, having no\n"
11023 "explicit chain to answer the rest from. nc's --method also takes cftp /\n"
11024 "cftp.approx, which draw iid states from the exact stationary law of a\n"
11025 "closed single-class product-form network by coupling from the past;\n"
11026 "they serve -a avg only and their rows carry Monte Carlo error.\n"
11027 "mam ports -a avg,\n"
11028 "prob, cdf, tran and internals, which are @SolverMAM's getProb and\n"
11029 "getProbMarg (the joint (level, phase) law of the queue and its\n"
11030 "per-class marginals, truncated at --cutoff when the model is open),\n"
11031 "getCdfRespT with getPerctRespT beside it, getTranAvg over --tspan (the\n"
11032 "reference forces method ldqbd there, so --method does not select it),\n"
11033 "and getMAMResult, the M/G/1-type internals of a single queue. fluid\n"
11034 "additionally reads -a tran, -a prob, -a cdf, -a var and -a aoi, which\n"
11035 "are @SolverFLD's getTranAvg (the metrics along the trajectory, over\n"
11036 "--tspan or, without one, the horizon the reference's own adaptive loop\n"
11037 "converges at), getProbAggr (a law FITTED to the fluid means, so it does\n"
11038 "not agree with the mva or nc answer by construction), getCdfRespT (the\n"
11039 "response-time law per station and class, read off a marked-fluid\n"
11040 "integration started from the steady state), getMoments/getTranAvgVar\n"
11041 "and getAvgAoI with getCdfAoI beside it -- the last needing method mfq\n"
11042 "and the Source/Queue/Sink topology the age laws are defined for. It\n"
11043 "further reads -a odes, which is\n"
11044 "@SolverFLD/exportODEs, and --notation for the form it writes, and -a\n"
11045 "jacobian, which is @SolverFLD/getJacobian: d f_i / d x_j of the drift,\n"
11046 "differentiated locally over the structure of the system, with the\n"
11047 "equilibria beside it under --equilibria, which needs the\n"
11048 "line-sage-rest backend --symbolic names. Only the smooth methods have\n"
11049 "a Jacobian: min(n,S) has none where the regime switches, so the\n"
11050 "min-scaled drifts are refused by the factor that carries the kink.\n"
11051 "nc, ba and ctmc run\n"
11052 "under every --arith backend, nc's cftp method excepted: its sampler\n"
11053 "works in the log domain, so it refuses 'exact' by name rather than\n"
11054 "answering in a field it does not live in. mdd does run under 'exact'\n"
11055 "(its level solve drops Householder for a rational least squares there),\n"
11056 "but the LEVEL AGGREGATION is still an approximation away from product\n"
11057 "form: exact arithmetic pins the fixed point, not the model. mam is\n"
11058 "double only (its phase-type fitter\n"
11059 "needs transcendental arithmetic) and so is ssa (its sample path is\n"
11060 "generated from exponential clocks) and fluid (LSODA). ba reports a\n"
11061 "BOUND, not an estimate, and ssa a simulation carrying Monte Carlo\n"
11062 "error; neither is comparable with an exact solver except as such.\n"
11064 "Arithmetic: 'exact' computes in arbitrary-precision rationals and\n"
11065 "reports numerator and denominator alongside the double value; 'real'\n"
11066 "computes in fixed high-precision binary floating point, at 50, 100 or\n"
11067 "200 digits (a request in between is rounded up to the next tier).\n"
11069 "--api arguments are a JSON object keyed by the MATLAB parameter names,\n"
11070 "e.g. {\"L\": [[0.6,0.4]], \"N\": [2,1], \"Z\": [1,0.5]}: a 2-D array is a\n"
11071 "matrix (row-major), a 1-D array a row vector, a bare number a scalar.\n"
11072 "A JSON number is read as its shortest round-tripping decimal, so 0.6 is\n"
11073 "3/5 in exact arithmetic; pass a string such as \"1/3\" for anything else.\n",
11090bool install_check() {
11091 bool has_warnings =
false;
11096 const auto warn = [&](
const std::string& text) {
11097 std::fflush(stdout);
11098 std::fprintf(stderr,
"%s\n", text.c_str());
11099 std::fflush(stderr);
11100 has_warnings =
true;
11103 std::printf(
"Checking LINE (C++)...\n");
11104 std::printf(
" line-cli %s\n", kVersion);
11106 std::printf(
"Checking Java runtime (JMT wrapper)...\n");
11107 const std::string java = line::jmt::detail::find_java();
11108 if (java.empty()) {
11109 warn(
"WARNING: no Java runtime was found in LINE_JAVA, JAVA_HOME or on PATH, so the "
11110 "JMT wrapper (-s jmt) cannot run. Install a JRE 8 or later.");
11112 std::printf(
" %s\n", java.c_str());
11115 std::printf(
"Checking JMT...\n");
11116 const std::string jmt_dir = line::jmt::detail::jmt_jar_path();
11117 if (jmt_dir.empty()) {
11123 warn(
"WARNING: JMT.jar was not found, this is required by the JMT wrapper. Download it "
11124 "from https://line-solver.sourceforge.net/latest/JMT.jar into common/, or point "
11125 "LINE_JMT_DIR at the folder holding it, or set LINE_JMT_DOWNLOAD=1 to let the first "
11126 "JMT solve fetch and verify it.");
11128 std::printf(
" %s/JMT.jar\n", jmt_dir.c_str());
11131 std::printf(
"Checking LQNS...\n");
11134 if (banner.empty())
11135 warn(
"WARNING: lqns is not installed, this is required by the LQNS wrapper (-s lqns) "
11136 "for layered models. Download it at: https://github.com/layeredqueuing/dist");
11138 warn(
"WARNING: the installed lqns is too old for LINE, which needs release 6 or "
11139 "later; it reports '" + banner +
"'. Upgrade it from: "
11140 "https://github.com/layeredqueuing/dist");
11145 std::printf(
"Checking qnsolver...\n");
11147 warn(
"WARNING: qnsolver is not installed, this is required by the qns methods of the "
11148 "LQNS wrapper (-s lqns on a Network). "
11149 "It ships with LQNS: https://github.com/layeredqueuing/dist");
11151 std::printf(
" qnsolver found\n");
11153 std::printf(
"Checking symbolic backend (line-sage-rest)...\n");
11155 warn(std::string(
"WARNING: Docker is not available, so the SageMath symbolic backend "
11156 "cannot start. It is the only computer algebra system this edition "
11157 "reaches and is required by the symbolic methods of "
11158 "SolverCTMC/SolverFluid. Install Docker, then run: "
11163 warn(std::string(
"WARNING: the line-sage-rest image is not present locally, this may be "
11164 "required by some LINE methods. Pull it with: "
11167 std::printf(
" %s (present locally)\n", image.c_str());
11171 std::printf(
"Completed. LINE has warnings.\n");
11173 std::printf(
"Success. LINE is ready to use.\n");
11174 return !has_warnings;
11179 std::printf(
"%-24s %-8s %-24s %s\n",
"function",
"domain",
"arithmetic",
"ported from");
11180 for (
const auto& e :
reg) {
11182 for (std::size_t k = 0; k < e.arith.size(); ++k) {
11183 if (k) modes +=
",";
11186 std::printf(
"%-24s %-8s %-24s %s\n", e.name.c_str(), e.domain.c_str(), modes.c_str(),
11187 e.reference.c_str());
11189 std::printf(
"\n%zu of ~480 API functions ported.\n",
reg.size());
11199 std::string source;
11200 if (path.empty()) {
11201 source =
"standard input";
11202 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
11204 source =
"'" + path +
"'";
11205 std::ifstream
in(path.c_str());
11207 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
11210 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
11211 throw line::InputError(
"no --api arguments given: pass --args <path> or a JSON object on "
11212 "standard input (read from " +
11215 return line::reg::Json::parse(text);
11216 }
catch (
const line::reg::Json::parse_error& e) {
11217 throw line::InputError(
"malformed --api arguments in " + source +
": " + e.what());
11232 std::string source;
11233 const std::string::size_type first = arg.find_first_not_of(
" \t\r\n");
11234 if (first != std::string::npos && arg[first] ==
'{') {
11235 source =
"the --generate argument";
11237 }
else if (arg.empty() || arg ==
"-") {
11238 source =
"standard input";
11239 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
11241 source =
"'" + arg +
"'";
11242 std::ifstream
in(arg.c_str());
11243 if (!in)
throw line::InputError(
"cannot open the --generate spec file " + source);
11244 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
11246 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
11247 throw line::InputError(
"no --generate specification given: pass a JSON object inline, a "
11248 "path to one, or `-` for standard input (read from " +
11251 return line::reg::Json::parse(text);
11252 }
catch (
const line::reg::Json::parse_error& e) {
11253 throw line::InputError(
"malformed --generate specification in " + source +
": " + e.what());
11259 if (!j.contains(key) || j.at(key).is_null())
return dflt;
11261 if (!v.is_number())
11262 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a number");
11263 const double d = v.get<
double>();
11264 if (d != std::floor(d))
11265 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a whole number");
11266 return static_cast<long>(d);
11270double gen_real(
const line::reg::Json& j,
const char* key,
double dflt) {
11271 if (!j.contains(key) || j.at(key).is_null())
return dflt;
11273 if (!v.is_number())
11274 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a number");
11275 return v.get<
double>();
11280 if (!j.contains(key) || j.at(key).is_null())
return dflt;
11282 if (!v.is_boolean())
11283 throw line::InputError(std::string(
"--generate: '") + key +
"' must be true or false");
11284 return v.get<
bool>();
11288std::string gen_str(
const line::reg::Json& j,
const char* key,
const std::string& dflt) {
11289 if (!j.contains(key) || j.at(key).is_null())
return dflt;
11291 if (!v.is_string())
11292 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a string");
11293 return v.get<std::string>();
11298 if (!j.contains(key) || j.at(key).is_null())
return dflt;
11300 if (!v.is_array() || v.size() != 2 || !v[0].is_number() || !v[1].is_number())
11302 "' must be a two-element array of numbers");
11312void gen_check_keys(
const line::reg::Json& j,
const char*
const* allowed, std::size_t n) {
11313 for (line::reg::Json::const_iterator it = j.begin(); it != j.end(); ++it) {
11315 for (std::size_t k = 0; k < n && !ok; ++k) ok = it.key() == allowed[k];
11318 for (std::size_t k = 0; k < n; ++k) {
11319 if (k) names +=
", ";
11320 names += allowed[k];
11323 "'; this kind reads: " + names);
11345std::string normalize_analysis(
const std::string& a) {
11347 if (a ==
"cdf-respt" || a ==
"cdfrespt")
return "cdf";
11348 if (a ==
"cdf-passt" || a ==
"cdfpasst")
return "cdfpasst";
11349 if (a ==
"first-passt" || a ==
"cdf-firstpasst" || a ==
"cdffirstpasst")
return "firstpasst";
11350 if (a ==
"first-passt-moments" || a ==
"firstpasst-moments" || a ==
"firstpasstmoments")
11351 return "firstpasstmom";
11352 if (a ==
"perct-respt" || a ==
"perctrespt")
return "perct";
11353 if (a ==
"tran-avg" || a ==
"tranavg")
return "tran";
11354 if (a ==
"tran-cdf-respt" || a ==
"trancdfrespt")
return "trancdf";
11355 if (a ==
"tran-cdf-passt" || a ==
"trancdfpasst")
return "trancdfpasst";
11356 if (a ==
"tran-prob" || a ==
"tranprob-sys-aggr")
return "tranprob";
11357 if (a ==
"generator")
return "gen";
11358 if (a ==
"state-space" || a ==
"statespace")
return "states";
11359 if (a ==
"reward-steady" || a ==
"rewardsteady")
return "reward";
11360 if (a ==
"reward-value" || a ==
"rewardvalue")
return "rewardvalue";
11361 if (a ==
"node-chain" || a ==
"node-chain-table")
return "nodechain";
11362 if (a ==
"fj-table" || a ==
"avg-fj" || a ==
"fjavgt")
return "fj";
11367 if (a ==
"prob-aggr" || a ==
"prob-sys" || a ==
"prob-sys-aggr")
return "prob";
11368 if (a ==
"prob-marg" || a ==
"probmarg")
return "marg";
11369 if (a ==
"prob-sys-marg" || a ==
"probsysmarg" || a ==
"sys-marg")
return "sysmarg";
11370 if (a ==
"sample-aggr" || a ==
"sample-sys" || a ==
"sample-sys-aggr")
return "sample";
11376 if (a ==
"stage")
return "avg";
11381std::vector<std::string> analysis_list(
const std::string& spec) {
11382 std::vector<std::string> out;
11383 std::size_t at = 0;
11384 while (at <= spec.size()) {
11385 const std::size_t comma = spec.find(
',', at);
11387 spec.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11389 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.front())))
11390 tok.erase(tok.begin());
11391 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.back())))
11394 throw line::InputError(
"-a takes a comma-separated list of analyses and one entry of '" +
11395 spec +
"' is empty");
11399 if (normalize_analysis(tok) ==
"all") {
11400 out.push_back(
"avg");
11401 out.push_back(
"sys");
11403 out.push_back(normalize_analysis(tok));
11405 if (comma == std::string::npos)
break;
11408 if (out.empty())
throw line::InputError(
"-a takes at least one analysis");
11413 std::string file, input =
"json", output =
"readable", solver =
"auto", analysis =
"avg";
11414 std::string arith =
"double", api, args;
11415 bool help =
false, help_all =
false, version =
false, list =
false;
11425 bool find_solver =
false, find_solver_all =
false;
11426 std::string find_solver_metric;
11428 bool install =
false;
11439 std::string generate;
11440 bool generate_given =
false;
11449 bool input_given =
false;
11464bool has_jsim_extension(
const std::string& file) {
11465 const std::string::size_type
dot = file.find_last_of(
'.');
11466 if (dot == std::string::npos)
return false;
11467 std::string ext = file.substr(dot + 1);
11468 for (std::size_t i = 0; i < ext.size(); ++i)
11469 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11470 return ext ==
"jsim" || ext ==
"jsimg" || ext ==
"jsimw";
11474bool has_pnml_extension(
const std::string& file) {
11475 const std::string::size_type
dot = file.find_last_of(
'.');
11476 if (dot == std::string::npos)
return false;
11477 std::string ext = file.substr(dot + 1);
11478 for (std::size_t i = 0; i < ext.size(); ++i)
11479 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11480 return ext ==
"pnml";
11484bool has_lqn_extension(
const std::string& file) {
11485 const std::string::size_type
dot = file.find_last_of(
'.');
11486 if (dot == std::string::npos)
return false;
11487 std::string ext = file.substr(dot + 1);
11488 for (std::size_t i = 0; i < ext.size(); ++i)
11489 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11490 return ext ==
"lqnx" || ext ==
"xml" || ext ==
"lqn";
11493Options parse_args(
int argc,
char** argv) {
11495 for (
int i = 1; i < argc; ++i) {
11496 std::string a = argv[i];
11497 auto next = [&](
const char* what) -> std::string {
11498 if (i + 1 >= argc)
throw line::InputError(std::string(
"missing value after ") + what);
11499 const char* v = argv[++i];
11504 k->
values +
"; got '" + v +
"'");
11507 if (a ==
"-h" || a ==
"--help") o.help =
true;
11508 else if (a ==
"--help-all" || a ==
"--help-full") o.help_all =
true;
11509 else if (a ==
"-V" || a ==
"--version") o.version =
true;
11510 else if (a ==
"--install") o.install =
true;
11511 else if (a ==
"--list-api") o.list =
true;
11512 else if (a ==
"--generate") {
11515 o.generate_given =
true;
11518 if (i + 1 < argc && (argv[i + 1][0] !=
'-' || argv[i + 1][1] ==
'\0'))
11519 o.generate = argv[++i];
11521 else if (a ==
"--find-solver" || a ==
"--find-method" || a ==
"--help-model") {
11522 o.find_solver =
true;
11525 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
11526 }
else if (a ==
"--find-solver-all" || a ==
"--find-method-all") {
11527 o.find_solver =
true;
11528 o.find_solver_all =
true;
11529 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
11531 else if (a ==
"-f" || a ==
"--file") o.file = next(
"-f");
11532 else if (a ==
"-i" || a ==
"--input") { o.input = next(
"-i"); o.input_given =
true; }
11533 else if (a ==
"-o" || a ==
"--output") o.output = next(
"-o");
11534 else if (a ==
"-s" || a ==
"--solver") o.solver = next(
"-s");
11535 else if (a ==
"-a" || a ==
"--analysis") o.analysis = next(
"-a");
11536 else if (a ==
"--arith") o.arith = next(
"--arith");
11537 else if (a ==
"-p" || a ==
"--port") {
11538 const std::string v = next(
"-p");
11539 const long n = std::atol(v.c_str());
11540 if (n < 1 || n > 65535)
11541 throw line::InputError(
"-p takes a TCP port in 1..65535 (got '" + v +
"')");
11542 o.port =
static_cast<int>(n);
11543 }
else if (a ==
"-m" || a ==
"--maxreq") {
11544 const std::string v = next(
"-m");
11545 const long n = std::atol(v.c_str());
11548 "-m is the number of requests the server serves before quitting and must be "
11549 "positive; omit it to serve indefinitely (got '" + v +
"')");
11550 o.maxreq =
static_cast<int>(n);
11552 else if (a ==
"--api") o.api = next(
"--api");
11553 else if (a ==
"--args") o.args = next(
"--args");
11554 else if (a ==
"--method") o.knobs.method = next(
"--method");
11555 else if (a ==
"--qrf-params") o.knobs.qrf_params = next(
"--qrf-params");
11556 else if (a ==
"--qrf-alpha") o.knobs.qrf_alpha = next(
"--qrf-alpha");
11557 else if (a ==
"--level") {
11558 const std::string v = next(
"--level");
11559 const int lv = std::atoi(v.c_str());
11560 if (lv < 1)
throw line::InputError(
"--level must be a positive integer (got '" + v +
"')");
11561 o.knobs.level = lv;
11563 else if (a ==
"--samples") {
11564 const std::string v = next(
"--samples");
11565 const double d = std::atof(v.c_str());
11567 throw line::InputError(
"--samples must be a positive count (got '" + v +
"')");
11568 o.knobs.samples =
static_cast<std::size_t
>(d);
11569 }
else if (a ==
"-d" || a ==
"--seed") {
11570 const std::string v = next(
"--seed");
11571 o.knobs.seed = std::strtoul(v.c_str(),
nullptr, 10);
11572 if (o.knobs.seed == 0)
11573 throw line::InputError(
"--seed must be a positive integer (got '" + v +
"')");
11574 }
else if (a ==
"--warmupfrac") {
11575 const std::string v = next(
"--warmupfrac");
11576 const double f = std::atof(v.c_str());
11577 if (!(f >= 0.0 && f < 1.0))
11579 "--warmupfrac is the fraction of the path discarded before the means are "
11580 "taken and must lie in [0,1) (got '" + v +
"')");
11581 o.knobs.warmupfrac = f;
11582 }
else if (a ==
"--pstar") {
11583 const std::string v = next(
"--pstar");
11584 const double ps = std::atof(v.c_str());
11587 "--pstar is the exponent of the fluid p-norm smoothing and must be positive "
11588 "(got '" + v +
"')");
11589 o.knobs.pstar = ps;
11590 }
else if (a ==
"--fluid-boundary") {
11591 const std::string v = next(
"--fluid-boundary");
11592 if (v !=
"reflect" && v !=
"diffusion")
11594 "--fluid-boundary (options.config.fluid_boundary) is 'reflect' (a fluid level held at a "
11595 "bound is the bound) or 'diffusion' (the mean of a reflected Brownian motion); got '" + v +
11597 o.knobs.fluid_boundary = v;
11598 }
else if (a ==
"--nonmkv") {
11599 const std::string v = next(
"--nonmkv");
11601 o.knobs.nonmkv = v;
11602 }
else if (a ==
"--busyperiod" || a ==
"--busyperiod-subnet") {
11605 const bool orders = (a ==
"--busyperiod");
11606 const std::string v = next(orders ?
"--busyperiod" :
"--busyperiod-subnet");
11607 std::vector<std::size_t>& into = orders ? o.knobs.busy_orders : o.knobs.busy_subnet;
11608 std::size_t at = 0;
11609 while (at <= v.size()) {
11610 const std::size_t comma = v.find(
',', at);
11611 const std::string tok =
11612 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11613 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos ||
11614 std::atol(tok.c_str()) < 1)
11616 std::string(orders ?
"--busyperiod takes a comma-separated list of "
11618 :
"--busyperiod-subnet takes a comma-separated list of "
11619 "1-based station indexes") +
11620 " (got '" + v +
"')");
11621 into.push_back(
static_cast<std::size_t
>(std::atol(tok.c_str())));
11622 if (comma == std::string::npos)
break;
11625 }
else if (a ==
"--tol") o.knobs.tol = std::atof(next(
"--tol").c_str());
11626 else if (a ==
"--iter_tol") o.knobs.iter_tol = std::atof(next(
"--iter_tol").c_str());
11627 else if (a ==
"--iter_max") o.knobs.iter_max = std::atoi(next(
"--iter_max").c_str());
11628 else if (a ==
"--max-states") {
11629 const std::string v = next(
"--max-states");
11630 const long long n = std::atoll(v.c_str());
11633 "--max-states truncates an open agent's queue-length dimension and takes a "
11634 "positive state count (got '" + v +
"')");
11635 o.knobs.max_states = n;
11637 else if (a ==
"--multiserver") o.knobs.multiserver = next(
"--multiserver");
11638 else if (a ==
"--fork-join" || a ==
"--fork_join")
11639 o.knobs.fork_join = next(
"--fork-join");
11640 else if (a ==
"--tran-points" || a ==
"--tran_points") {
11641 const std::string v = next(
"--tran-points");
11642 const long n = std::atol(v.c_str());
11645 "--tran-points is the number of points on the transient grid and needs at "
11646 "least two, a start and an end (got '" + v +
"')");
11647 o.knobs.tran_points =
static_cast<std::size_t
>(n);
11649 else if (a ==
"--mdd-tol" || a ==
"--mdd_tol") {
11650 const std::string v = next(
"--mdd-tol");
11651 const double d = std::atof(v.c_str());
11653 throw line::InputError(
"--mdd-tol must be a positive tolerance (got '" + v +
"')");
11654 o.knobs.mdd_tol = d;
11655 }
else if (a ==
"--mdd-maxiter" || a ==
"--mdd_maxiter") {
11656 const std::string v = next(
"--mdd-maxiter");
11657 const long n = std::atol(v.c_str());
11660 "--mdd-maxiter must be a positive sweep count (got '" + v +
"')");
11661 o.knobs.mdd_maxiter =
static_cast<int>(n);
11663 else if (a ==
"--fj-accuracy") {
11664 const std::string v = next(
"--fj-accuracy");
11665 const long n = std::atol(v.c_str());
11668 "--fj-accuracy is the FJ_codes truncation C of the queue-length difference "
11669 "between the two fork-join branches and must be at least 1 (got '" + v +
"')");
11670 o.knobs.fj_accuracy =
static_cast<int>(n);
11671 }
else if (a ==
"--fj-tmode") {
11672 const std::string v = next(
"--fj-tmode");
11673 if (v !=
"NARE" && v !=
"Sylvester")
11675 "--fj-tmode selects how computeT.m solves for the T matrix and is 'NARE' (the "
11676 "Riccati route, the default) or 'Sylvester' (the fixed-point iteration); got '" +
11678 o.knobs.fj_tmode = v;
11679 }
else if (a ==
"--sync-phases") {
11680 const std::string v = next(
"--sync-phases");
11681 char* end =
nullptr;
11682 const long n = std::strtol(v.c_str(), &end, 10);
11683 if (v.empty() || end ==
nullptr || *end !=
'\0' || n < 0)
11685 "--sync-phases is the number of phases of the synchronous phase expansion and "
11686 "must be a non-negative integer (got '" + v +
"')");
11687 o.knobs.sync_phases =
static_cast<int>(n);
11688 }
else if (a ==
"--mapmsg-quantization") {
11689 const std::string v = next(
"--mapmsg-quantization");
11690 char* end =
nullptr;
11691 const long n = std::strtol(v.c_str(), &end, 10);
11692 if (v.empty() || end ==
nullptr || *end !=
'\0' || n < 2)
11694 "--mapmsg-quantization is the number of patience regimes of the MAP/M/s+G "
11695 "reneging analyzer and must be an integer of at least 2 (got '" + v +
"')");
11696 o.knobs.mapmsg_quantization =
static_cast<int>(n);
11697 }
else if (a ==
"--timescale") {
11698 const std::string v = next(
"--timescale");
11699 if (v !=
"auto" && v !=
"discrete" && v !=
"continuous")
11701 "--timescale decides whether the model is read on a slot lattice and is "
11702 "'auto' (the default), 'discrete' or 'continuous'; got '" + v +
"'");
11703 o.knobs.timescale = v;
11705 else if (a ==
"--force") {
11706 o.knobs.force =
true;
11708 else if (a ==
"--cutoff") {
11709 const std::string v = next(
"--cutoff");
11710 if (v.find(
',') != std::string::npos || v.find(
';') != std::string::npos) {
11711 o.knobs.cutoff_mat = parse_cutoff_matrix(v);
11712 if (o.knobs.cutoff_mat.empty())
11714 "--cutoff takes a number or a per-(station,class) matrix written "
11715 "'r1c1,r1c2;r2c1,r2c2' (got '" + v +
"')");
11717 const double d = std::atof(v.c_str());
11720 "--cutoff must be a positive job count per open class (got '" + v +
"')");
11721 o.knobs.cutoff = d;
11723 }
else if (a ==
"--tspan" || a ==
"--timespan") {
11724 const std::string v = next(
"--tspan");
11730 std::string::size_type sep = v.find(
':');
11731 if (sep == std::string::npos) sep = v.find(
',');
11734 const double lo = sep == std::string::npos ? 0.0 : std::atof(v.substr(0, sep).c_str());
11735 const double hi = std::atof(
11736 (sep == std::string::npos ? v : v.substr(sep + 1)).c_str());
11739 if (!(hi > lo) || !(lo >= 0.0) || !std::isfinite(hi))
11741 "--tspan must be a finite horizon 0 <= t0 < t1, given as <t1>, <t0>:<t1> or "
11742 "<t0>,<t1> (got '" + v +
"')");
11745 }
else if (a ==
"-n" || a ==
"--node") {
11746 const std::string v = next(
"--node");
11747 const long n = std::atol(v.c_str());
11749 throw line::InputError(
"--node must be a positive 1-based node index (got '" + v +
11751 o.knobs.node =
static_cast<std::size_t
>(n);
11752 }
else if (a ==
"-c" || a ==
"--class") {
11753 const std::string v = next(
"--class");
11754 const long c = std::atol(v.c_str());
11756 throw line::InputError(
"--class must be a positive 1-based class index (got '" + v +
11758 o.knobs.jobclass =
static_cast<std::size_t
>(c);
11759 }
else if (a ==
"--marg-states" || a ==
"--marg_states") {
11761 const std::string v = next(
"--marg-states");
11762 std::size_t at = 0;
11763 while (at <= v.size()) {
11764 const std::size_t comma = v.find(
',', at);
11765 const std::string tok =
11766 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11767 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
11769 "--marg-states takes a comma-separated list of non-negative job counts "
11770 "(got '" + v +
"')");
11771 o.knobs.marg_states.push_back(std::atol(tok.c_str()));
11772 if (comma == std::string::npos)
break;
11775 }
else if (a ==
"--state") {
11779 const std::string v = next(
"--state");
11780 std::size_t at = 0;
11781 while (at <= v.size()) {
11782 const std::size_t comma = v.find(
',', at);
11783 const std::string tok =
11784 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11785 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
11787 "--state is the state vector -a prob asks about and takes a "
11788 "comma-separated list of non-negative counts (got '" + v +
"')");
11789 o.knobs.state.push_back(std::atol(tok.c_str()));
11790 if (comma == std::string::npos)
break;
11793 }
else if (a ==
"--events") {
11794 const std::string v = next(
"--events");
11795 const double d = std::atof(v.c_str());
11798 "--events is the length of a sampled trajectory and must be a positive event "
11799 "count (got '" + v +
"')");
11800 o.knobs.events =
static_cast<std::size_t
>(d);
11801 }
else if (a ==
"--timestep") {
11802 const std::string v = next(
"--timestep");
11803 const double d = std::atof(v.c_str());
11804 if (!(d > 0.0) || !std::isfinite(d))
11806 "--timestep is the fixed output step of a transient analysis and must be a "
11807 "positive finite time (got '" + v +
"')");
11808 o.knobs.timestep = d;
11809 }
else if (a ==
"--percentiles") {
11810 const std::string v = next(
"--percentiles");
11811 std::size_t at = 0;
11812 while (at <= v.size()) {
11813 const std::size_t comma = v.find(
',', at);
11814 const std::string tok =
11815 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11818 "--percentiles takes a comma-separated list of levels (got '" + v +
"')");
11819 double p = std::atof(tok.c_str());
11826 if (p > 1.0) p /= 100.0;
11827 if (!(p > 0.0) || !(p < 1.0))
11829 "--percentiles levels lie strictly inside (0,1) as fractions or (0,100) "
11830 "as percents; the 100th percentile of an unbounded law is not finite "
11831 "(got '" + tok +
"')");
11832 o.knobs.percentiles.push_back(p);
11833 if (comma == std::string::npos)
break;
11836 }
else if (a ==
"--reward-name" || a ==
"--reward_name") {
11837 o.knobs.reward_name = next(
"--reward-name");
11838 }
else if (a ==
"--notation") {
11839 const std::string v = next(
"--notation");
11842 o.knobs.notation = v;
11843 }
else if (a ==
"--symbolic") {
11847 o.knobs.symbolic = next(
"--symbolic");
11848 }
else if (a ==
"--equilibria") o.knobs.equilibria =
true;
11849 else if (a ==
"--perm-engine") {
11852 o.knobs.method_perm = next(
"--perm-engine");
11853 }
else if (a ==
"--transient-method") {
11856 o.knobs.transient_method = next(
"--transient-method");
11857 }
else if (a ==
"--rate-sched") {
11858 o.knobs.rate_sched = next(
"--rate-sched");
11859 }
else if (a ==
"--ctmc-tv-ngrid") {
11860 const std::string v = next(
"--ctmc-tv-ngrid");
11861 const long n = std::atol(v.c_str());
11863 throw line::InputError(
"--ctmc-tv-ngrid is the grid size of the rate_sched "
11864 "propagator and must be an integer >= 2 (got '" + v +
"')");
11865 o.knobs.ctmc_tv_ngrid =
static_cast<std::size_t
>(n);
11866 }
else if (a ==
"--fau-epsilon") {
11867 const std::string v = next(
"--fau-epsilon");
11868 const double d = std::atof(v.c_str());
11869 if (!(d > 0.0) || !std::isfinite(d))
11871 "--fau-epsilon is the probability mass the transient grid may discard and "
11872 "must be a positive finite number (got '" + v +
"')");
11873 o.knobs.fau_epsilon = d;
11874 }
else if (a ==
"--fau-delta") {
11875 const std::string v = next(
"--fau-delta");
11876 const double d = std::atof(v.c_str());
11877 if (!(d >= 0.0) || !std::isfinite(d))
11879 "--fau-delta is the occupancy below which a state is dropped and must be a "
11880 "nonnegative finite number (got '" + v +
"')");
11881 o.knobs.fau_delta = d;
11882 }
else if (a ==
"--cdf-algorithm") {
11885 o.knobs.cdf_algorithm = next(
"--cdf-algorithm");
11886 }
else if (a ==
"--passage-from") o.knobs.passage_from = next(
"--passage-from");
11887 else if (a ==
"--passage-into") o.knobs.passage_into = next(
"--passage-into");
11888 else if (a ==
"--passage-method") o.knobs.passage_method = next(
"--passage-method");
11889 else if (a ==
"--passage-taboo") o.knobs.passage_taboo = next(
"--passage-taboo");
11890 else if (a ==
"--passage-tset") {
11891 o.knobs.passage_tset = next(
"--passage-tset");
11892 if (o.knobs.passage_tset.empty())
11893 throw line::InputError(
"--passage-tset needs at least one time ('t1,t2,...')");
11895 else if (a ==
"--passage-orders")
11896 o.knobs.passage_orders =
static_cast<std::size_t
>(std::stoul(next(
"--passage-orders")));
11897 else if (a ==
"--no-interlocking") o.knobs.no_interlocking =
true;
11898 else if (a ==
"--interlock-method") o.knobs.interlock_method = next(
"--interlock-method");
11899 else if (a ==
"--interlock-maxpaths")
11900 o.knobs.interlock_maxpaths = std::stod(next(
"--interlock-maxpaths"));
11901 else if (a ==
"--interlock-refpath-scope")
11902 o.knobs.interlock_refpath_scope = next(
"--interlock-refpath-scope");
11903 else if (a ==
"--lld") {
11905 const std::string v = next(
"--lld");
11906 const std::size_t eq = v.find(
'=');
11907 if (eq == std::string::npos || eq == 0 || eq + 1 == v.size())
11908 throw line::InputError(
"--lld takes STATION=EXPR, a processor or task name and an "
11909 "expression in n (got '" + v +
"')");
11910 const std::string st = v.substr(0, eq);
11911 if (o.knobs.lld.count(st))
11913 o.knobs.lld[st] = v.substr(eq + 1);
11914 }
else if (a ==
"--nlp-alpha") {
11915 const std::string v = next(
"--nlp-alpha");
11916 const double x = std::atof(v.c_str());
11917 if (!(x > 0.0) || !std::isfinite(x))
11918 throw line::InputError(
"--nlp-alpha is the softmin sharpness and must be a positive finite "
11919 "number (got '" + v +
"')");
11920 o.knobs.nlp_alpha = x;
11921 }
else if (a ==
"--nlp-warm") {
11922 const long w = std::stol(next(
"--nlp-warm"));
11923 if (w < 0)
throw line::InputError(
"--nlp-warm counts warm-start sweeps and must be >= 0");
11924 o.knobs.nlp_warm = w;
11925 }
else if (a ==
"--nlp-maxiter") {
11926 const long m = std::stol(next(
"--nlp-maxiter"));
11927 if (m < 0)
throw line::InputError(
"--nlp-maxiter is an iteration budget and must be >= 0");
11928 o.knobs.nlp_maxiter = m;
11929 }
else if (a ==
"--nlp-fork") o.knobs.nlp_fork = next(
"--nlp-fork");
11930 else if (a ==
"--warmstart") o.knobs.warmstart = next(
"--warmstart");
11931 else if (a ==
"--mol-iter-max") {
11932 const long m = std::stol(next(
"--mol-iter-max"));
11933 if (m < 0)
throw line::InputError(
"--mol-iter-max is an iteration budget and must be >= 0");
11934 o.knobs.mol_iter_max = m;
11935 }
else if (a ==
"--mol-iter-tol") {
11936 const std::string v = next(
"--mol-iter-tol");
11937 const double x = std::atof(v.c_str());
11938 if (!(x > 0.0) || !std::isfinite(x))
11939 throw line::InputError(
"--mol-iter-tol must be a positive finite number (got '" + v +
"')");
11940 o.knobs.mol_iter_tol = x;
11941 }
else if (a ==
"--mol-relax-factor") {
11942 const std::string v = next(
"--mol-relax-factor");
11943 const double x = std::atof(v.c_str());
11944 if (!(x > 0.0) || !(x <= 1.0))
11945 throw line::InputError(
"--mol-relax-factor must lie in (0, 1] (got '" + v +
"')");
11946 o.knobs.mol_relax_factor = x;
11947 }
else if (a ==
"--layer-solver") o.knobs.layer_solver = next(
"--layer-solver");
11948 else if (a ==
"--stage-solver") o.knobs.stage_solver = next(
"--stage-solver");
11949 else if (a ==
"--env-init") o.knobs.env_init = next(
"--env-init");
11950 else if (a ==
"--map-env") o.knobs.map_env = next(
"--map-env");
11951 else if (a ==
"--map-env-method") o.knobs.map_env_method = next(
"--map-env-method");
11952 else if (a ==
"--map-env-maxstages")
11953 o.knobs.map_env_maxstages =
static_cast<std::size_t
>(std::stoul(next(
"--map-env-maxstages")));
11954 else if (a ==
"--ln-transient") o.knobs.ln_transient = next(
"--ln-transient");
11955 else if (a ==
"--ln-transient-channels")
11956 o.knobs.ln_transient_channels = next(
"--ln-transient-channels");
11957 else if (a ==
"--sens-method") o.knobs.sens_method = next(
"--sens-method");
11958 else if (a ==
"--sens-scheme") o.knobs.sens_scheme = next(
"--sens-scheme");
11959 else if (a ==
"--sens-step") {
11960 const std::string v = next(
"--sens-step");
11961 const double h = std::atof(v.c_str());
11962 if (!(h > 0.0) || !(h < 1.0))
11964 "--sens-step is the RELATIVE rate perturbation and must lie in (0,1) (got '" +
11966 o.knobs.sens_step = h;
11968 else if (a ==
"--uq-solver") o.knobs.uq_solver = next(
"--uq-solver");
11969 else if (a ==
"--keep") o.knobs.keep =
true;
11970 else if (a ==
"--verbose") o.knobs.verbose =
true;
11971 else if (a ==
"--remote") o.knobs.remote =
true;
11972 else if (a ==
"--remote-url") {
11975 o.knobs.remote_url = next(
"--remote-url");
11976 o.knobs.remote =
true;
11978 else if (a ==
"--timeout") {
11979 const std::string v = next(
"--timeout");
11980 const long s = std::atol(v.c_str());
11982 throw line::InputError(
"--timeout is a deadline in seconds and must be positive "
11983 "(got '" + v +
"')");
11984 o.knobs.timeout_seconds =
static_cast<int>(s);
11986 else if (a ==
"--repeat") {
11987 const std::string v = next(
"--repeat");
11988 const long n = std::atol(v.c_str());
11990 throw line::InputError(
"--repeat must be a positive run count (got '" + v +
"')");
11991 o.knobs.repeat =
static_cast<int>(n);
11994 else if (a ==
"--ldes-tranfilter") {
11995 const std::string v = next(
"--ldes-tranfilter");
11996 if (v !=
"mser5" && v !=
"fixed" && v !=
"none")
11998 "--ldes-tranfilter selects the warmup filter and is mser5, fixed or none (got '" +
12000 o.knobs.ldes_tranfilter = v;
12002 else if (a ==
"--ldes-warmupfrac") {
12003 const std::string v = next(
"--ldes-warmupfrac");
12004 const double d = std::atof(v.c_str());
12005 if (!(d >= 0.0 && d < 1.0))
12007 "--ldes-warmupfrac is the fraction of the run the fixed filter discards and "
12008 "lies in [0,1) (got '" + v +
"')");
12009 o.knobs.ldes_warmupfrac = d;
12011 else if (a ==
"--ldes-cimethod") {
12012 const std::string v = next(
"--ldes-cimethod");
12013 if (v !=
"obm" && v !=
"bm" && v !=
"spectral" && v !=
"none")
12015 "--ldes-cimethod selects the confidence-interval estimator and is obm, bm, "
12016 "spectral or none (got '" + v +
"')");
12017 o.knobs.ldes_cimethod = v;
12019 else if (a ==
"--ldes-cnvgon") o.knobs.ldes_cnvgon =
true;
12020 else if (a ==
"--ldes-cnvgtol") {
12023 const std::string v = next(
"--ldes-cnvgtol");
12024 const double d = std::atof(v.c_str());
12025 if (!(d > 0.0 && d < 1.0))
12027 "--ldes-cnvgtol is a RELATIVE precision target and lies in (0,1) (got '" + v +
12029 o.knobs.ldes_cnvgtol = d;
12030 o.knobs.ldes_cnvgon =
true;
12032 else if (a ==
"--ldes-slotted") o.knobs.ldes_slotted =
true;
12033 else if (a ==
"--slotted") o.knobs.slotted =
true;
12034 else if (a ==
"--slotlength") {
12036 const std::string v = next(
"--slotlength");
12037 const double d = std::atof(v.c_str());
12040 "--slotlength is the slot of the discrete time scale and must be positive "
12041 "(got '" + v +
"')");
12042 o.knobs.slotlength = d;
12043 o.knobs.slotted =
true;
12045 else if (a ==
"--ldes-slotlength") {
12047 const std::string v = next(
"--ldes-slotlength");
12048 const double d = std::atof(v.c_str());
12051 "--ldes-slotlength is the slot of the discrete time scale and must be positive "
12052 "(got '" + v +
"')");
12053 o.knobs.ldes_slotlength = d;
12054 o.knobs.ldes_slotted =
true;
12056 else if (a ==
"--jmt-replications") {
12057 const std::string v = next(
"--jmt-replications");
12058 const long n = std::atol(v.c_str());
12061 "--jmt-replications is a positive count of independent runs (got '" + v +
"')");
12062 o.knobs.jmt_replications =
static_cast<int>(n);
12064 else if (a ==
"--ldes-replications") {
12065 const std::string v = next(
"--ldes-replications");
12066 const long n = std::atol(v.c_str());
12069 "--ldes-replications is a positive count of independent runs (got '" + v +
"')");
12070 o.knobs.ldes_replications =
static_cast<int>(n);
12072 else if (a ==
"--ldes-numthreads") {
12073 const std::string v = next(
"--ldes-numthreads");
12074 const long n = std::atol(v.c_str());
12077 "--ldes-numthreads is a positive worker count (got '" + v +
"')");
12078 o.knobs.ldes_numthreads =
static_cast<int>(n);
12080 else if (a ==
"--ldes-maxtime") {
12081 const std::string v = next(
"--ldes-maxtime");
12082 const double d = std::atof(v.c_str());
12085 "--ldes-maxtime is a wall-clock budget in seconds and must be positive (got '" +
12087 o.knobs.ldes_maxtime = d;
12089 else if (a ==
"--ldes-initsol") {
12092 const std::string v = next(
"--ldes-initsol");
12094 while (b <= v.size()) {
12095 const std::size_t e = v.find(
',', b);
12096 const std::string tok =
12097 v.substr(b, e == std::string::npos ? std::string::npos : e - b);
12100 "--ldes-initsol is a comma-separated placement with no empty entry (got '" +
12102 o.knobs.ldes_initsol.push_back(std::atof(tok.c_str()));
12103 if (e == std::string::npos)
break;
12107 else if (a ==
"--ldes-rest-url") o.knobs.ldes_rest_url = next(
"--ldes-rest-url");
12108 else if (a ==
"-v" || a ==
"--verbosity") {
12109 const std::string v = next(a.c_str());
12114 if (v !=
"silent" && v !=
"standard" && v !=
"normal" && v !=
"debug" &&
12117 "-v takes silent, standard (the JAR spells it normal) or debug; got '" + v +
12119 o.knobs.verbosity = (v ==
"normal") ?
"standard" : v;
12121 else if (!a.empty() && a[0] ==
'-')
12148 if (o.file.empty() && !g_stdin_loaded)
12149 throw line::InputError(
"--find-solver reports on a model; name one with -f");
12150 const bool layered = o.file.empty()
12154 if (!o.input_given && layered)
12156 "--find-solver reports on a flat Network model; a layered one is solved by -s ln "
12157 "and -s lqns, which this port reaches through the -i lqnx path");
12158 if (o.output !=
"readable" && o.output !=
"json")
12160 "'; --find-solver reports as: readable, json");
12163 net.
get_struct(), o.find_solver_metric, o.find_solver_all);
12164 if (o.output !=
"json") {
12175 for (std::size_t i = 0; i < rows.size(); ++i) {
12177 c[
"solver"] = rows[i].solver;
12178 c[
"method"] = rows[i].method;
12179 c[
"runnable"] = rows[i].runnable;
12180 c[
"methodClass"] = rows[i].method_class;
12181 c[
"reason"] = rows[i].reason;
12184 c[
"metrics"] = rows[i].metrics;
12185 cands.push_back(c);
12188 body[
"type"] =
"FindSolver";
12189 body[
"candidates"] = cands;
12190 j[
"findSolver"] = body;
12191 emit_document(dump_document(j));
12208 if (!o.api.empty()) {
12209 if (o.output !=
"readable" && o.output !=
"json")
12211 "'; accepted forms are: readable, json");
12214 if (o.output ==
"json")
12215 emit_document(dump_document(result, 2));
12223 if (!o.input_given && has_lqn_extension(o.file)) o.input =
"lqnx";
12227 if (!o.input_given && has_jsim_extension(o.file)) o.input =
"jsimg";
12229 if (!o.input_given && has_pnml_extension(o.file)) o.input =
"pnml";
12242 const bool sniffable = o.input !=
"lqnx" && o.input !=
"xml" && o.input !=
"srvn" && o.input !=
"pnml" &&
12243 o.input.compare(0, 4,
"jsim") != 0;
12244 if (sniffable && !o.file.empty() && !has_pnml_extension(o.file) &&
12247 else if (sniffable && o.file.empty() && g_stdin_loaded &&
12250 if (o.input ==
"lqnx" || o.input ==
"xml" || o.input ==
"srvn") {
12254 std::string lqn_file = o.file;
12255 std::string staged;
12258 const char* tmpdir = std::getenv(
"TMPDIR");
12259 std::string path = std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-srvn-XXXXXX";
12260 std::vector<char> buf(path.begin(), path.end());
12261 buf.push_back(
'\0');
12262 const int fd = ::mkstemp(&buf[0]);
12265 staged.assign(&buf[0]);
12266 std::ofstream(staged.c_str(), std::ios::binary) << doc;
12270 const std::string& p;
12271 ~Unstage() {
if (!p.empty()) std::remove(p.c_str()); }
12273 if (o.output !=
"readable" && o.output !=
"json" && o.output !=
"layers")
12275 "'; accepted forms on the layered path are: readable, "
12283 const std::vector<std::string> as = analysis_list(o.analysis);
12284 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
12285 const int rc = solve_lqn_dispatch(o.arith, o.solver, as[i], o.output, lqn_file,
12287 if (rc != 0)
return rc;
12290 return solve_lqn_dispatch(o.arith, o.solver, as.back(), o.output, lqn_file, o.knobs);
12297 if (o.input ==
"jsim" || o.input ==
"jsimg" || o.input ==
"jsimw")
12298 g_jsim_input =
true;
12299 else if (o.input ==
"pnml")
12300 g_pnml_input =
true;
12301 else if (o.input !=
"json")
12303 "the model-solving path reads -i json for a Network model, -i jsim|jsimg|jsimw "
12304 "for a JMT simulation document, -i pnml for a place/transition net and "
12305 "-i lqnx|xml|srvn for a layered one (got '" + o.input +
"')");
12320 if (o.output ==
"jsim" || o.output ==
"jsimg" || o.output ==
"jsimw") {
12329 if (o.knobs.seed) wopt.
seed =
static_cast<long>(o.knobs.seed);
12330 if (o.knobs.samples) wopt.
max_samples =
static_cast<double>(o.knobs.samples);
12335 if (o.output !=
"readable" && o.output !=
"json")
12337 "'; accepted forms are: readable, json, jsimg");
12338 g_json_output = (o.output ==
"json");
12341 const std::vector<std::string> as = analysis_list(o.analysis);
12342 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
12343 const int rc = solve_model_dispatch(o.arith, o.solver, as[i], o.file, o.knobs);
12344 if (rc != 0)
return rc;
12347 return solve_model_dispatch(o.arith, o.solver, as.back(), o.file, o.knobs);
12367 if (fd_ < 0)
return;
12368 const char* tmpdir = std::getenv(
"TMPDIR");
12370 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-cap-XXXXXX";
12371 std::vector<char> buf(path.begin(), path.end());
12372 buf.push_back(
'\0');
12373 tfd_ = ::mkstemp(&buf[0]);
12374 if (tfd_ < 0)
return;
12375 path_.assign(&buf[0]);
12376 std::fflush(fd_ == 1 ? stdout : stderr);
12377 saved_ = ::dup(fd_);
12388 if (!path_.empty()) {
12389 std::remove(path_.c_str());
12397 if (path_.empty())
return std::string();
12398 std::ifstream in(path_.c_str());
12399 std::string out((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
12401 std::remove(path_.c_str());
12410 if (saved_ < 0)
return;
12411 std::fflush(fd_ == 1 ? stdout : stderr);
12412 ::dup2(saved_, fd_);
12444 reset_invocation_state(
false);
12448 return "line-cli: cannot create a capture file for the response\n";
12455 err = std::string(
"line-cli: [") + e.
id() +
"] " + e.what() +
"\n";
12456 }
catch (
const std::exception& e) {
12458 err = std::string(
"line-cli: unexpected failure: ") + e.what() +
"\n";
12460 std::string out = cap.
take();
12463 return err.empty() ? out : out + err;
12479 std::printf(
"--------------------------------------------------------------------\n");
12480 std::printf(
"LINE Solver - Command Line Interface (C++)\n");
12481 std::printf(
"Copyright (c) 2012-2026, QORE Lab, Imperial College London\n");
12482 std::printf(
"Version %s. All rights reserved.\n", kVersion);
12483 std::printf(
"--------------------------------------------------------------------\n");
12484 std::printf(
"Running in server mode on port %d.\n", base.port);
12486 std::printf(
"Quitting after %d request(s).\n", base.maxreq);
12487 std::fflush(stdout);
12490 while (base.maxreq == 0 || served < base.maxreq) {
12491 const bool ok = server.
serve_one([&](
const std::string& msg) -> std::string {
12492 const std::string::size_type nl = msg.find(
'\n');
12493 if (nl == std::string::npos)
12494 return "line-cli: the request's first line is the argument list and its "
12495 "remainder is the model document; this message has no newline\n";
12496 const std::string argline = msg.substr(0, nl);
12497 const std::string model = msg.substr(nl + 1);
12499 const char* tmpdir = std::getenv(
"TMPDIR");
12501 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-req-XXXXXX";
12502 std::vector<char> nb(path.begin(), path.end());
12503 nb.push_back(
'\0');
12504 const int mfd = ::mkstemp(&nb[0]);
12505 if (mfd < 0)
return "line-cli: cannot stage the client model\n";
12507 path.assign(&nb[0]);
12509 std::ofstream mf(path.c_str());
12518 std::vector<std::string> toks;
12519 std::string::size_type at = 0;
12520 while (at <= argline.size()) {
12521 const std::string::size_type comma = argline.find(
',', at);
12522 toks.push_back(argline.substr(
12523 at, comma == std::string::npos ? std::string::npos : comma - at));
12524 if (comma == std::string::npos)
break;
12527 std::string result;
12528 if (toks.size() < 2) {
12529 result =
"line-cli: the argument list needs at least two tokens; the first two "
12530 "are replaced by --file and the staged model path\n";
12532 toks[0] =
"--file";
12534 std::vector<char*> argv;
12535 std::vector<std::string> store;
12536 store.push_back(
"line-cli");
12537 for (std::size_t i = 0; i < toks.size(); ++i) store.push_back(toks[i]);
12538 for (std::size_t i = 0; i < store.size(); ++i)
12539 argv.push_back(
const_cast<char*
>(store[i].c_str()));
12542 Options ro = parse_args(
static_cast<int>(argv.size()), &argv[0]);
12550 result = std::string(
"line-cli: [") + e.
id() +
"] " + e.what() +
"\n";
12553 std::remove(path.c_str());
12582 if (!spec.is_object())
12584 "{\"kind\":\"network\",\"queues\":3,\"closedClasses\":1}");
12585 const std::string kind = gen_str(spec,
"kind",
"network");
12586 if (kind ==
"network") {
12587 static const char*
const keys[] = {
12588 "kind",
"seed",
"name",
"queues",
"delays",
"openClasses",
"closedClasses",
12589 "schedStrat",
"routingStrat",
"distribution",
"cclassJobLoad",
"varyingServiceRates",
12590 "multiServerQueues",
"randomCSNodes",
"multiChainCS",
"topology"};
12591 gen_check_keys(spec, keys,
sizeof(keys) /
sizeof(keys[0]));
12593 if (spec.contains(
"seed")) g.
set_seed(gen_int(spec,
"seed", 0));
12603 const std::string topo = gen_str(spec,
"topology",
"rand");
12604 if (topo ==
"rand")
12606 else if (topo ==
"cyclic")
12610 "'; accepted forms are: rand, cyclic");
12613 const long nq = gen_int(spec,
"queues", 1);
12614 const long nd = gen_int(spec,
"delays", -1);
12615 const long no = gen_int(spec,
"openClasses", 0);
12616 const long nc = gen_int(spec,
"closedClasses", 1);
12618 static_cast<int>(no),
static_cast<int>(
nc));
12622 if (kind ==
"layered") {
12623 static const char*
const keys[] = {
12624 "kind",
"seed",
"name",
"clients",
"levels",
"tasks",
"processors",
"populationRange",
12625 "thinkTimeRange",
"taskMultiRange",
"procMultiRange",
"hostDemandRange",
12626 "synchCallRange",
"taskInfProbability",
"procInfProbability"};
12627 gen_check_keys(spec, keys,
sizeof(keys) /
sizeof(keys[0]));
12629 if (spec.contains(
"seed")) g.
set_seed(gen_int(spec,
"seed", 0));
12639 const long ncl = gen_int(spec,
"clients", 1);
12640 const long nlv = gen_int(spec,
"levels", 1);
12641 const long ntk = gen_int(spec,
"tasks", 1);
12642 const long npr = gen_int(spec,
"processors", 1);
12644 g.
generate(
static_cast<int>(ncl),
static_cast<int>(nlv),
static_cast<int>(ntk),
12645 static_cast<int>(npr));
12653 "'; accepted forms are: network, layered");
12667int dispatch(
const Options& in,
bool allow_server,
bool no_args) {
12672 if (!o.knobs.verbosity.empty()) g_verbosity = o.knobs.verbosity;
12679 : (g_verbosity ==
"debug" || g_verbosity ==
"verbose")
12686 if (o.help || no_args) {
12687 print_brief_help();
12691 std::printf(
"line-cli %s\n", kVersion);
12714 "-p runs the solver as a server, which this caller did not permit");
12718 if (!o.file.empty())
12720 "-p runs the solver as a server, where each request carries its own model; "
12721 "-f names a model on the command line and the two cannot both be the source");
12733 reset_invocation_state(
false);
12738 std::vector<std::string> docs;
12745 std::vector<std::string> store;
12746 store.push_back(
"line-cli");
12747 for (std::size_t i = 0; i < req.
argv.size(); ++i) store.push_back(req.
argv[i]);
12748 std::vector<char*> argv;
12749 for (std::size_t i = 0; i < store.size(); ++i) argv.push_back(&store[i][0]);
12763 }
catch (
const InterruptRequested& e) {
12765 res.
error = e.what();
12770 res.
error = e.what();
12773 }
catch (
const std::exception& e) {
12775 res.
error = std::string(
"unexpected failure: ") + e.what();
12782 g_json_sink =
nullptr;
12783 g_interrupt_cb =
nullptr;
12784 g_interrupt_user =
nullptr;
12786 reset_invocation_state(
false);
12792 return dispatch(parse_args(argc, argv),
true, argc == 1);
12794 std::fprintf(stderr,
"line-cli: [%s] %s\n", e.
id().c_str(), e.what());
12796 }
catch (
const std::exception& e) {
12797 std::fprintf(stderr,
"line-cli: unexpected failure: %s\n", e.what());
The -s ag entry point: gates, fixed point, mean measures.
Direct invocation of a single API function from named JSON arguments.
SolverAUTO.listValidMethods: the method names THIS MODEL can actually run.
Redirect a file descriptor into a temporary file for a scope, and give back what was written to it.
std::string take()
Restore the descriptor and read back what was written.
Base error for the multiprecision C++ port.
const std::string & id() const noexcept
The full identifier, e.g.
std::string category() const
unsupported, numerical, input, external, internal, or empty for LINE:Error.
std::string kind() const
The <kind> part, or empty for LINE:Error.
A tool, binary, service or bridge LINE depends on is missing or failed: LINE:external:<kind>.
The algorithm cannot produce a trustworthy answer on this instance: LINE:numerical:<kind>.
A refusal: the feature, method or analysis is not covered: LINE:unsupported:<kind>.
const EnvStage< T > & stage(std::size_t e) const
std::size_t nstages() const
A random lqn::LqnBuilder<T> source, configured once and then drawn from.
void set_host_demand_range(const Range &r)
Activity host demands.
lqn::LqnBuilder< T > generate(int num_clients, int num_levels, int num_tasks, int num_processors)
void set_proc_inf_probability(double p)
Probability that a processor is infinite-server rather than PS.
void set_synch_call_range(const Range &r)
Mean number of synchronous calls on a call arc.
void set_model_name(const std::string &nm)
The name of the generated model.
void set_population_range(const Range &r)
Reference-task populations.
void set_think_time_range(const Range &r)
Client think times.
void set_proc_multi_range(const Range &r)
Processor multiplicities, for the processors that are not infinite-server.
const std::string & model_name() const
void set_task_multi_range(const Range &r)
Task multiplicities, for the tasks that are not infinite-server.
void set_task_inf_probability(double p)
Probability that a server task is infinite-server rather than FCFS.
void set_seed(long long seed)
Reseed the single stream every draw comes from.
A random qn::Network<T> source, configured once and then drawn from.
void set_cclass_job_load(const std::string &load)
high, medium, low, or randomize: the population band of a closed class.
void set_model_name(const std::string &nm)
The name given to the generated model.
void set_seed(long long seed)
Reseed the single stream every draw comes from, as MATLAB rng(seed).
void set_topology(TopologyKind kind)
Pick one of the two shipped topology generators; validated like set_topology_fcn.
void set_distribution(const std::string &d)
Exp, Erlang, HyperExp, or randomize.
void set_multi_chain_cs(bool v)
Classes are partitioned into random chains and switching is confined to a chain, instead of the defau...
void set_sched_strat(const std::string &strat)
fcfs, ps, inf, lcfs, lcfspr, siro, sjf, ljf, sept, lept, or randomize.
void set_random_cs_nodes(bool v)
Each link gets a ClassSwitch node inserted on a coin flip.
qn::Network< T > generate(int num_queues, int num_delays, int num_oclass, int num_cclass)
The full form.
void set_routing_strat(const std::string &strat)
Probabilities, Random, or randomize.
void set_varying_service_rates(bool v)
Service means spread over 2^-6 .
void set_multi_server_queues(bool v)
Queues get 1..40 servers instead of exactly one.
LnSensTable< T > get_sensitivity_table(const sens::SensOptions &sopt)
Port of @SolverLN/getSensitivityTable: solve the ensemble, then concatenate each layer solver's own t...
LnTranSolution get_tran_avg()
Port of @SolverLN/getTranAvg: the block-diagonal aggregate transient.
std::size_t nlayers() const
std::vector< LnCdf > get_cdf_respt()
Port of @SolverLN/getCdfRespT: the per-entry response-time distribution.
LnSolution< T > get_ensemble_avg()
Port of getEnsembleAvg: run the iteration and aggregate onto LQN elements.
const std::vector< qn::Layer< T > > & layers() const
LqnStruct< T > build() const
Flatten into the struct SolverLN consumes.
const LqnModel< T > & model() const
The layered model solved by lqns or lqsim.
static bool is_stochastic_method(const std::string &method)
Only the lqsim methods draw random numbers.
static std::string version()
The version banner of the local binary, empty when there is none.
A layer network: everything a NetworkStruct holds, plus the LQN back-mapping.
A network plus its refreshed NetworkStruct.
std::size_t nvars_of(std::size_t ind) const
Total local-variable width of node ind (1-based).
std::size_t stateful_index(std::size_t ind) const
1-based stateful index of node ind, 0 when the node is not stateful.
std::size_t nof_nodes() const
std::map< std::pair< std::size_t, std::size_t >, Matrix< T > > P
P[(r,s)] is an (nnodes x nnodes) block; absent means all zero.
std::size_t phases_of(std::size_t ist, std::size_t r) const
sn.phases(i,r): the order of the process representation.
std::vector< Reward > reward
std::vector< std::size_t > stateful_nodes
1-based node indices, ascending
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::map< std::size_t, CacheParam< T > > nodeparam
Cache parameters by 1-based NODE index; only Cache nodes have an entry.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< NodeDef > nodes
every node, in creation order
std::vector< std::size_t > station_to_node
(nstations) 1-based node index
std::vector< Region > regions
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
NetworkStruct< T > & raw_struct()
The struct WITHOUT refreshing it, for a caller that is still building.
RAII guard: opens a run on construction, closes it on scope exit.
static void reset()
Forget any open run (used after an interrupted solve).
static void set_verbose(VerboseLevel level)
Set the session verbosity.
A listening socket; one connection is served at a time.
bool serve_one(Fn serve)
Accept one connection, complete the handshake, read ONE text message and hand it to serve; send what ...
The CLI's argument vector, reachable in-process.
The keys a LINE solver accepts under options.config, in all four codebases.
Docker primitives for the backends that legitimately ship an image.
The SolverENV entry surface: a port of the analyzer selection that @@SolverENV/SolverENV....
Reader for the LINE model.json interchange of an ENVIRONMENT model into an env::Environment<T>.
The exception types the port throws.
Port of @@SolverFLD/getJacobian, all four of its outputs.
The fluid solver's outermost entry point: @@SolverFLD/runAnalyzer.m's method resolution over solver_f...
The log-driven half of SolverJMT: linkAndLog, parseLogs, parseTranState, parseTranRespT,...
Port of @@JMTIO: a refreshed NetworkStruct written out as a JMT .jsimg simulation model.
Read a JMT .jsim / .jsimg / .jsimw model into a qn::Network.
Random layered-queueing-network generation: the C++ twin of MATLAB @LayeredNetworkGenerator,...
The NATIVE LDES engine for LAYERED (LQN) models, the C++ twin of jline/solvers/ldes/handlers/Solver_s...
int dispatch(const Options &in, bool allow_server, bool no_args)
Everything main did, with the arguments already in hand.
int run_invocation(Options o)
Everything one invocation does once the arguments are in hand.
#define LINE_CLI_TABLE_LADDER(FN)
int find_solver_report(const Options &o)
--find-solver: which solvers and methods can analyze the model named by -f.
int run_generate(const Options &o)
--generate: draw a random model and print it, solving nothing.
std::string run_invocation_captured(Options o, int &rc)
Run one invocation with stdout captured, and return what it printed.
int run_server(const Options &base)
-p/--port: serve solve requests over a WebSocket, as LineWebSocketServer does.
Running progress log of a LINE solver run (the "solver console").
model.json with type: "LayeredNetwork" -> LqnStruct, via LqnBuilder.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
LqnModel -> .lqnx, a port of matlab/src/lang/layered/@LayeredNetwork/writeXML.m.
The flat-Network path of @@SolverLQNS: its qns methods, served by qnsolver of the RADS/LQNS distribut...
Port of @NetworkSolver/mapEnvApprox.m: the solver-agnostic random-environment approximation of a netw...
The stage solvers map_env_approx injects, one per runner that can be a caller.
Boundary marshalling for host bindings (MATLAB MEX, pybind11, the JSON CLI).
Classification of a solution method, as printed in the solver banner: "<accuracy>,...
mva::AvgResult< T > solver_ag_run_analyzer(const qn::NetworkStruct< T > &L, const AgOptions &opt)
SolverAG.runAnalyzer: the converged agents as mean measures.
Matrix< T > sn_declared_marginal(const qn::NetworkStruct< T > &sn)
The (nstations x nclasses) per-class job counts of the model's OWN state.
bool nonmkv_enabled(const std::string &method)
Resolve options.config.nonmkv to NonmarkovOptions::enabled.
AutoEnvChoice auto_choose_env_solver(const std::string &method, AutoMode mode=AutoMode::HEUR)
The Environment arm of chooseSolverHeur / chooseSolverExact / chooseSolverSim.
std::string auto_find_solver_table(const std::vector< SolverCandidate > &rows)
The rows as an aligned text table, the form the CLI and a console caller want.
AutoLayeredChoice auto_choose_layered_solver(const std::string &method, bool has_cache_task, AutoMode mode=AutoMode::HEUR)
chooseLayeredSolver plus the LayeredNetwork arm of chooseAvgSolverHeur.
const char * auto_env_stage_solver(AutoEnv s)
The EnvOptions::stage_solver (and --stage-solver) name of a candidate ensemble.
const char * auto_solver_name(AutoSolver s)
AutoToken auto_resolve_token(const std::string &raw, const qn::NetworkStruct< T > &sn)
resolveMethodToken with its third arm: an intent, a family[.submethod], or else a BARE method name,...
std::vector< AutoSolver > auto_proposed_solvers(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
delegate's proposed order: the chosen solver, then every feasible candidate in slot order.
AutoChoice auto_choose_solver_mode(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
chooseSolver: the selection mode picks the ranking, and every mode but the two learned ones keeps the...
std::vector< SolverCandidate > auto_find_solver(const qn::NetworkStruct< T > &sn, const std::string &metric=std::string(), bool show_all=false)
SolverAUTO.findSolver: which solvers and solver methods can analyze this model, and for the ones that...
const char * auto_env_name(AutoEnv s)
const char * auto_layered_name(AutoLayered s)
The layered names are the CLI's own tokens, because that is what the choice is spent on: ln....
BaBounds< T > ba_bounds(const qn::NetworkStruct< T > &L, const BaOptions &opt)
Port of SolverBA.getBounds.
mva::AvgResult< T > solver_ba_run_analyzer(const qn::NetworkStruct< T > &L, const BaOptions &opt_in)
Port of @@SolverBA/runAnalyzer.m for the lang='matlab' path.
std::string resolve_method(const std::string &method)
Port of runAnalyzer's method aliases: default is the geometric upper bound, bare auto is the AUTO com...
int main_body(int argc, char **argv)
The binary's main, so line_cli_main.cpp stays ten lines.
Response run(const Request &req)
Run one invocation and return what it produced.
Matrix< T > solver_ctmc_sample(const NetworkStruct< T > &sn, const CtmcSamplePath< T > &path, std::size_t ind)
Port of @@SolverCTMC/sample: the walk restricted to ONE stateful node's local block.
Matrix< T > solver_ctmc_sample_aggr(const NetworkStruct< T > &sn, const CtmcSamplePath< T > &path, std::size_t ind)
Port of @@SolverCTMC/sampleAggr: one node's per-class counts over time.
std::vector< CdfCurve< T > > solver_ctmc_cdf_sys_respt(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getCdfSysRespT.m: the per-chain SYSTEM response-time CDF, indexed by chain.
T solver_ctmc_jointaggr(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_jointaggr: P(the network holds exactly these per-class counts),...
CtmcTranProb< T > ctmc_get_tran_prob_sys(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr)
Port of @@SolverCTMC/getTranProbSys.m: pi(t), labelled by the whole network state with its phases.
CtmcSamplePath< T > solver_ctmc_sample_sys(const NetworkStruct< T > &sn, const CtmcOptions &opt_in, std::size_t nevents, unsigned long seed=23000)
Port of @@SolverCTMC/sampleSys: a marked walk on the whole network state.
CtmcTranProb< T > ctmc_get_tran_prob_sys_aggr(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr)
Port of @@SolverCTMC/getTranProbSysAggr.m: pi(t), labelled by the network's per-(station,...
mva::AvgResult< T > solver_ctmc_avg_table(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const std::string &method)
Port of @@SolverCTMC/runAnalyzer.m's result assembly: solve, then apply the metric filter @@NetworkSo...
CtmcMddSolution< T > solver_ctmc_mdd_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const mdd::MddMcdOptions &mcdopt=mdd::MddMcdOptions())
Solve with the mdd method.
CtmcGenerator< T > ctmc_get_infgen(const NetworkStruct< T > &sn, const CtmcSolution< T > &d)
@@SolverCTMC/getInfGen.m, a pure alias of getGenerator in the reference.
std::vector< T > solver_ctmc_margaggr(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_margaggr: per STATION, P(that station holds exactly these per-class counts).
std::vector< CtmcSensRank< T > > solver_ctmc_sensitivity_ranking(const NetworkStruct< T > &sn, const CtmcOptions &opt, const std::vector< CtmcSensParam< T > > ¶ms, const std::vector< T > &reward)
Port of @@SolverCTMC/getSensitivityRanking: rank parameters by influence.
std::vector< std::vector< CdfCurve< T > > > solver_ctmc_cdf_respt(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getCdfRespT.m: the per-(station, class) response-time CDF, indexed [ist-1][r-1].
std::vector< T > solver_ctmc_marg(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_marg: per STATION, P(that station is in exactly its local slice of state),...
std::vector< T > solver_ctmc_avg_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, std::vector< std::string > *names=nullptr)
Port of @@SolverCTMC/getAvgReward: the steady-state expected rewards.
CtmcStateSpace< T > ctmc_get_state_space(const NetworkStruct< T > &, const CtmcSolution< T > &d)
Port of @@SolverCTMC/getStateSpace.m.
CtmcTransient< T > solver_ctmc_transient_analyzer(const NetworkStruct< T > &sn_arg, const CtmcOptions &opt, const T &t0, const T &t1, const std::vector< T > &grid=std::vector< T >())
Port of solver_ctmc_transient_analyzer.m.
CtmcFirstPassageMoments< T > ctmc_firstpasst_moments(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, std::size_t nmax, const Matrix< double > &C)
Port of @@SolverCTMC/getFirstPassTMoments.m: moments of order 1..nmax of the first passage time from ...
T solver_ctmc_joint(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_joint: P(the network is in exactly state).
Matrix< T > ctmc_get_state_space_aggr(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getStateSpaceAggr.m: the per-(station, class) job counts of every state,...
CtmcFirstPassage ctmc_cdf_firstpasst(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, const Matrix< double > &C, std::vector< double > tset, const std::string &method="expm")
Port of @@SolverCTMC/getCdfFirstPassT.m: the distribution of the FIRST PASSAGE TIME from state set A ...
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
CtmcAnySolution< T > solver_ctmc_analyzer_any(const NetworkStruct< T > &sn, const CtmcOptions &opt)
The entry point a caller who does not know which path a model needs should use: pick the WAITQ walk w...
CtmcTranProb< T > ctmc_get_tran_prob(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr, std::size_t ind)
Port of @@SolverCTMC/getTranProb.m: pi(t) over the whole chain, labelled by one node's local state.
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
CtmcTranProb< T > ctmc_get_tran_prob_aggr(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr, std::size_t ind)
Port of @@SolverCTMC/getTranProbAggr.m: pi(t), labelled by one node's per-class job counts.
std::vector< std::vector< T > > solver_ctmc_tran_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, std::vector< T > *tout=nullptr, std::vector< std::string > *names=nullptr)
Port of @@SolverCTMC/getTranReward: E[r(X(t))] = sum_s pi_t(s) r(s).
CtmcReward< T > solver_ctmc_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, std::size_t tmax=1000)
Port of solver_ctmc_reward.m.
Matrix< T > solver_ctmc_sample_sys_aggr(const NetworkStruct< T > &sn, const CtmcSamplePath< T > &path)
Port of @@SolverCTMC/sampleSysAggr: the same walk, reported as per-(station, class) job counts rather...
EnvStatevecSolution< T > solver_env_statevec(Environment< T > &e, const EnvStatevecOptions< T > &o)
Solve in one call, for a caller with no use for the solver object.
EnvAnalyzerSolution< T > solver_env(Environment< T > &e, const EnvOptions &o)
SolverENV.init's analyzer selection: solve the environment with the coupling o.method names.
FluidKpTransient solver_fluid_tran_avg_var(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of @@SolverFLD/getTranAvgVar: the queue-length VARIANCE along the trajectory,...
double aoi_cdf(const AoiMe &me, double t)
getCdfAoI: F(t) = 1 - S(t) with S the survival function of the age law.
double fluid_prob_aggr(const qn::NetworkStruct< T > &sn, const FluidSolution &sol, std::size_t ist, double *logp_out=nullptr)
Port of @@SolverFLD/getProbAggr: the probability that station ist holds the marginal population of th...
AoiTopology aoi_is_aoi(const qn::NetworkStruct< T > &sn)
Port of aoi_is_aoi.m.
std::string solver_fluid_export_odes(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const std::string ¬ation="scalar", const std::string &model_name="model")
Port of @@SolverFLD/exportODEs.m, which hands options.method to solver_fluid_symodes UNRESOLVED: defa...
FluidJacobian fluid_jacobian(const FluidSymSystem &sys, const FluidSymbolicOptions &opt=FluidSymbolicOptions())
Jacobian, drift and equilibria of the mean-field vector field.
FluidSolution solver_fluid_run_analyzer(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, qn::NetworkStruct< T > *sn_out=nullptr, qn::NetworkStruct< T > *refreshed_out=nullptr, solvers::CacheMetrics< T > *cache_out=nullptr)
Port of @@SolverFLD/runAnalyzer.m: resolve the method, route to the function the reference routes to,...
std::vector< std::vector< FluidPassage > > solver_fluid_cdf_respt(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=201)
Port of @@SolverFLD/getCdfRespT: the response-time law of every (station, class) pair,...
FluidSymSystem fluid_symodes(const qn::NetworkStruct< T > &sn, const std::string &method_in, double pstar, const std::vector< double > &init_sol)
Build the symbolic system of sn under opt.method.
std::vector< FluidTranPoint > solver_fluid_run_transient(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=101)
-a tran / @@SolverFLD/getTranAvg with the method HONOURED, which is the one place the reference does ...
bool is_layered_json(const std::string &path)
True when a file is a LayeredNetwork model.json rather than an .lqnx.
std::string jsim_stage_stdin(const std::string &text)
Write a piped XML model document to a temporary file and return its path.
qn::Network< T > read_network_json(const std::string &path)
Parse a model.json file into a qn::Network<T>.
qn::Network< T > read_jsim(const std::string &path, const std::string &name=std::string())
Read a JSIM document into a Network.
lqn::LqnModel< T > read_lqnx_model(const std::string &path)
Read a .lqnx file into the model-level LqnModel, which write_lqnx accepts back.
lqn::LqnStruct< T > read_layered_model(const std::string &path)
Read a layered model from any of its interchanges: the LINE model.json, the LQNS ....
std::string jmt_write_jsim(const qn::NetworkStruct< T > &sn, const JmtWriteOptions &opt)
Port of @@JMTIO/writeJSIM.m: serialize sn as a JMT .jsimg document.
env::Environment< T > build_environment_from_json(const detail::json &root)
Build an env::Environment<T> from a parsed model.json envelope.
env::Environment< T > read_environment_json(const std::string &path)
Parse a model.json file into an env::Environment<T>.
qn::Network< T > build_network_from_json(const detail::json &root)
Build a qn::Network<T> from a parsed model.json envelope.
bool docker_daemon_available()
True if the Docker daemon is reachable.
detail::json network_json_envelope(const qn::NetworkStruct< T > &sn)
The complete model.json envelope: {format, version, model}.
const char * error_id(const Error &e)
Stable identifier for an error, for mexErrMsgIdAndTxt and for mapping to a host exception class.
bool is_layered_json_text(const std::string &text)
True when a DOCUMENT ALREADY IN MEMORY is a LayeredNetwork model.json.
qn::Network< T > pnml_load(const std::string &path, const std::string &net_id=std::string())
Read one net of a PNML place/transition document into a Network.
JmtReplication< T > jmt_transient_replications(const qn::NetworkStruct< T > &sn, const JmtOptions &opt)
Port of the transient ensemble of @@SolverJMT/runAnalyzer.m (default over a finite timespan).
JmtSysTrace< T > jmt_sample_sys_aggr(const qn::NetworkStruct< T > &sn, std::size_t num_events, const JmtOptions &opt)
Port of sampleSysAggr: every station's trajectory on one time grid.
JmtNodeTrace< T > jmt_sample_aggr(const qn::NetworkStruct< T > &sn, std::size_t node, std::size_t num_events, const JmtOptions &opt)
Port of sampleAggr: the queue-length trajectory of one node.
std::vector< std::string > jmt_list_valid_methods()
Port of SolverJMT.listValidMethods.
std::size_t jmt_transient_replication_count(const JmtOptions &opt, const std::string &what)
How many JSIM runs a transient estimate is over: the gate the two transient arms share.
JmtResult< T > solver_jmt_run_analyzer(const qn::NetworkStruct< T > &sn, const JmtOptions &opt_in)
Port of @@SolverJMT/runAnalyzer.m, the jsim and jmva arms.
std::map< std::pair< std::size_t, std::size_t >, std::vector< std::pair< double, double > > > jmt_get_cdf_resp_t(const qn::NetworkStruct< T > &sn, const JmtOptions &opt, bool seed_from_steady=true)
Port of getCdfRespT: the empirical response-time distribution per (station, class),...
std::string jmt_removed_method_refusal(const std::string &method)
The migration sentence for a method name SolverJMT no longer has; empty for any other name.
std::pair< std::vector< double >, std::vector< std::vector< double > > > jmt_get_tran_prob_aggr(const qn::NetworkStruct< T > &sn, std::size_t station, const JmtOptions &opt, std::vector< std::vector< double > > &states_out)
Port of getTranProbAggr: the transient distribution of one station's aggregate state,...
JmtProbAggr jmt_prob_aggr(const qn::NetworkStruct< T > &sn, const JmtOptions &opt, std::size_t target_station=0, const std::vector< double > &target=std::vector< double >())
Port of getProbAggr and getProbSysAggr, both off ONE instrumented run.
const char * sched_to_text(SchedStrategy s)
const char * event_to_text(EventType e)
LnColumn
A column of the shared layered average table, as line-cli prints it.
bool ln_defined(const lqn::LqnStruct< T > &lsn, const LnResult &r, std::size_t i, LnColumn c)
Does the element at i HAVE the quantity in column c?
std::vector< LnEntryCdf > ldes_ln_cdf_respt(const LnResult &r, std::size_t nentries)
The empirical response time CDF of every ENTRY, the getCdfRespTLN of the other codebases: one [F(t),...
solvers::FjMetrics< double > ldes_fj_metrics(const qn::NetworkStruct< T > &sn, const LdesResult &r)
ldesParseForkJoin: the engine's "forkJoin" rows mapped onto the Fork/Join pairs of sn by (fork name,...
engine::LnResult ldes_ln_engine_solve(const lqn::LqnStruct< T > &lsn, const LdesOptions &o)
Simulate a layered model in process.
LdesResult solver_ldes_text(const std::string &doc, const LdesOptions &o, const std::vector< std::string > &extra_flags=std::vector< std::string >())
Runs one LDES simulation on a model.json DOCUMENT and parses its result.
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
std::string fmt(const char *f, double v)
bool in(const std::string &s, std::initializer_list< const char * > set)
const std::string * get(const Element *el, const std::string &key)
el.get(key), with Python's None as a null pointer.
std::string name(const Element *el, const std::string &key="name")
std::string srvn_file_to_lqnx(const std::string &path)
Translate the SRVN file path to .lqnx text, model name = its basename without extension.
LqnModel< T > read_srvn_model(const std::string &path)
Read an SRVN file (.in, or a .lqn that is not XML) into the intermediate LqnModel,...
std::string lqnx_to_string(const LqnModel< T > &m, const std::string &model_name=std::string("LQN"), bool use_abstract_names=false)
The .lqnx document as a STRING, for a caller with no file to write to – the CLI's model-generation mo...
bool is_srvn_path(const std::string &path)
Whether path should be read as SRVN: a .in file, or a .lqn file that is not XML.
mva::AvgResult< T > solve_network_run_analyzer(const qn::NetworkStruct< T > &L, const QnsOptions &opt)
Port of @@SolverLQNS/runAnalyzerNetwork.m and solver_qns.m: SolverLQNS on a flat Network.
const std::string & lqns_version()
The version banner of the local lqns, empty when there is none.
bool lqns_is_available()
True when lqns is installed AND is a release this port speaks.
bool qnsolver_is_available()
Port of SolverLQNS.hasQnsolver: a native qnsolver binary on the PATH.
std::vector< T > mam_percentiles_from_cdf(const RespTCdf< T > &cdf, const std::vector< double > &pcts)
Port of the CDF path of @@SolverMAM/getPerctRespT.m: linear interpolation of the response-time CDF at...
TranResult< T > solver_mam_get_tran_avg(const qn::NetworkStruct< T > &L, const MamOptions &opt_in)
Port of @@SolverMAM/getTranAvg.m: transient queue length, utilization and throughput.
std::vector< RespTCdf< T > > solver_mam_get_cdf_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt)
@@SolverMAM/getCdfRespT.m: the response-time CDF per class.
mva::AvgResult< T > solver_mam_run_analyzer(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of @@SolverMAM/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@N...
ProbTable< T > solver_mam_get_prob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
@@SolverMAM/getProb.m: the joint (level, phase) table at a node.
std::vector< std::vector< T > > solver_mam_get_perct_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m: response-time percentiles per class.
bool mam_has_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Whether getPerctRespT reads the FJ_codes table rather than inverting a CDF.
std::vector< T > solver_mam_get_prob_marg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
@@SolverMAM/getProbMarg.m: P(n jobs of one class) at a station.
qsys::BmapM1Result< T > solver_mam_get_mam_result(const qn::NetworkStruct< T > &L)
@@SolverMAM/getMAMResult.m: the M/G/1-type internals of a single queue.
const std::map< std::string, std::vector< std::string > > & table()
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
AggrResult< T > solver_mva_get_prob_aggr(const qn::NetworkStruct< T > &L, const AvgResult< T > &avg, std::size_t ist, const std::string &method="default")
T solver_mva_get_prob_norm_const_aggr(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of @@SolverMVA/getProbNormConstAggr.m: log G.
std::string resolve_method(const qn::NetworkStruct< T > &L, const std::string &method)
Port of SolverMVA.resolveMethod: the feature-driven default -> rqna upgrade for a bursty single-class...
MargResult< T > solver_mva_get_prob_marg(const qn::NetworkStruct< T > &L, const AvgResult< T > &avg, std::size_t ist, std::size_t r, const std::vector< long > &states, const std::string &method="default")
Port of @@SolverMVA/getProbMarg.m: P(n jobs of class r at station i) for the states in states (or the...
AggrResult< T > solver_mva_get_prob_sys_aggr(const qn::NetworkStruct< T > &L, const AvgResult< T > &avg, const std::string &method="default")
Port of @@SolverMVA/getProbSysAggr.m: the joint probability of the model's whole state across all sta...
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
Port of sn_get_arvr_from_tput: the arrival rate each station sees, from the throughputs and the class...
AvgResult< T > solver_mva_run_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt_in, const Matrix< T > &init_sol)
Port of @@SolverMVA/runAnalyzer.m for the lang='matlab' path: gate, solve, convert,...
NcCftpSolution< T > solver_nc_cftp(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const NcCftpOptions &cftpopt)
Solve with the cftp / cftp.approx method.
mva::AvgResult< T > solver_nc_run_analyzer(const qn::NetworkStruct< T > &L_in, const NcSolverOptions &opt_in)
T solver_nc_getprob_sys_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const std::vector< int > &nvec, const std::string &engine="exact")
Port of @@SolverNC/getProbSysMarg.m.
T solver_nc_getprob_sys_aggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir)
Port of @@SolverNC/getProbSysAggr.m.
NcSolution< T > solver_nc_cftp_solution(const NcCftpSolution< T > &s)
A cftp solve in the shape every other SolverNC analyzer returns, so the runner's metric filter applie...
T solver_nc_joint(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double *lG_out)
Port of solver_nc_joint.m: the probability of the WHOLE system state.
std::vector< double > solver_nc_busyp(const qn::NetworkStruct< T > &sn, const std::vector< std::size_t > &subnet, const std::vector< std::size_t > &orders)
Mean busy period of order n for a set of stations.
NcQueueLengthDist< T > solver_nc_getprob_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t ist)
Port of @@SolverNC/getProbMarg.m: the TOTAL queue-length distribution.
NcMargResult< T > solver_nc_margaggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double lG)
Port of solver_nc_margaggr.m.
bool is_stochastic_method(const std::string &method)
Port of SolverNC.isStochasticMethod.
NcSolution< T > solver_nc_solve(const qn::NetworkStruct< T > &L_in, const NcSolverOptions &opt_in)
The gates, the multiserver conversion and the dispatch of @@SolverNC/runAnalyzer.m,...
CdfRespTResult< T > solver_nc_cdf_respt(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Port of @@SolverNC/getCdfRespT.m.
NcMargResult< T > solver_nc_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double lG)
Port of solver_nc_marg.m: the DETAILED marginal, which weighs the station's internal arrangement and ...
std::vector< std::vector< int > > MarginalState
A state, as this port expresses it: nir[i][r] jobs of class r at station i.
mva::AvgResult< T > solver_nc_avg_table(const qn::NetworkStruct< T > &L_in, const NcSolution< T > &d, const std::string &origmethod)
Port of @@SolverNC/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@Ne...
std::string solver_nc_cftp_supports(const qn::NetworkStruct< T > &sn)
The cftp model-class gate as a public predicate.
NcldMethod
The load-dependent methods this port dispatches.
std::vector< std::vector< int > > multichoose_rows(int n, int k)
All n-vectors of nonnegative integers summing to k, in MATLAB multichoose(n,k) order.
bool ncld_method_try(const std::string &s, NcldMethod &out)
Map a method name to its enum; false when the name is not one of them.
FeatureSet fluid_feature_set(const std::string &method)
Marginal< T > to_marginal(const NetworkStruct< T > &sn, std::size_t ist, const std::vector< T > &state_i, const std::vector< std::size_t > &phasesz, const std::vector< std::size_t > &phaseshift, std::size_t nvar=0)
Port of State.toMarginal for a STATION, one state row at a time.
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
FeatureSet mva_feature_set(const std::string &raw_method)
SolverMVA.getFeatureSet, plus getMethodFeatureSet's per-method deltas.
void feature_gate(const std::string &solver, const FeatureSet &declared, const NetworkStruct< T > &sn, const std::string &requested_method="", const std::string &resolved_method="")
runAnalyzerChecks: refuse a model the solver does not declare, by name.
std::string api_render_readable(const Json &result)
Human-readable rendering of the object api_invoke returns, for -o readable.
ArithSpec parse_arith(const std::string &text)
Parse –arith.
Json api_invoke(const std::string &name, const std::string &arith, const Json &args)
Invoke one API function.
SensTable< T > solver_sensitivity_table(qn::NetworkStruct< T > &sn, const SensOptions &opt, bool exact_available, const std::function< mva::MvaSolution< T >()> &solve)
Build the sensitivity table of sn under solve.
void put(Matrix< double > &A, std::size_t r0, std::size_t c0, const Matrix< double > &S)
A(r0:, c0:) = S.
double dot(const std::vector< double > &a, const std::vector< double > &b)
The inner product of a row vector with a column held as a vector.
MapEnvDecision needs_map_env(const qn::FeatureSet &declared, const qn::NetworkStruct< T > &sn, const MapEnvConfig &cfg=MapEnvConfig())
needsMapEnv: does this model need the environment image, and would the image make it solvable?
std::vector< std::string > chain_class_labels(const qn::NetworkStruct< T > &sn)
(ClassA ClassB), the JobClasses column: which classes a chain holds.
std::vector< bool > node_table_hidden_classes(const line::qn::NetworkStruct< T > &sn)
The classes getAvgNodeTable omits: the auxiliary retrieval classes that Cache.setRetrievalSystem mint...
env::EnvStageAvgFn< double > nc_stage_fn(const nc::NcSolverOptions &opt)
NC stages, bound to the caller's own NcSolverOptions.
SysResult< T > solver_get_avg_sys(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgSys.m.
FjMetrics< double > fj_metrics_to_double(const FjMetrics< T > &in)
The same metrics in double precision, for the facade and the CLI.
line::mva::AvgResult< T > avg_result_from_sim(const line::qn::NetworkStruct< T > &sn, const line::Matrix< double > &QN, const line::Matrix< double > &UN, const line::Matrix< double > &RN, const line::Matrix< double > &TN, const std::vector< double > &CN, const std::vector< double > &XN, const std::string &method)
The station AvgResult of a solver whose runner returns its own solution type, i.e.
NodeMetrics< T > node_metrics(const line::qn::NetworkStruct< T > &sn, const line::mva::AvgResult< T > &r)
bool config_value_ok(const ConfigKey &k, const char *value)
Whether value is admissible for k: always true when the key lists no values.
mva::AvgResult< T > run_avg(const qn::NetworkStruct< T > &sn, const std::string &solver, const qn::FeatureSet &declared, const MapEnvConfig &cfg, Run run, StageFn stage_fn, const std::string &requested_method="default")
The getAvg funnel: run the model, or its environment image when the ONLY thing in the way is a non-re...
ChainResult< T > solver_get_avg_node_chain(const qn::NetworkStruct< T > &sn, const Matrix< T > &QNn, const Matrix< T > &UNn, const Matrix< T > &RNn, const Matrix< T > &WNn, const Matrix< T > &ANn, const Matrix< T > &TNn)
Port of @@NetworkSolver/getAvgNodeChain.m: the NODE table aggregated by chain.
ChainResult< T > solver_get_avg_chain(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgChain.m: the station table aggregated by chain.
mva::AvgResult< T > map_env_approx(const qn::NetworkStruct< T > &sn, const std::string &solver, const MapEnvConfig &cfg, StageFn stage_fn, const std::string &requested_method="default")
mapEnvApprox: solve the model through the random-environment image of its non-renewal processes.
const ConfigKey * config_key_for_flag(const char *flag)
The config key a line-cli flag carries; nullptr when the flag carries none.
std::vector< std::vector< DefaultCdfCurve > > solver_default_cdf_respt(const qn::NetworkStruct< T > &sn, const Matrix< T > &RN)
The NetworkSolver base-class response-time CDF: an exponential law with the right mean per (station,...
std::vector< std::string > chain_names(std::size_t nchains)
Chain1, Chain2, ... – the reference's own chain labels.
env::EnvStageAvgFn< double > fluid_stage_fn(const fluid::FluidOptions &opt)
Fluid stages, bound to the caller's own FluidOptions.
env::EnvStageAvgFn< double > mva_stage_fn(const mva::MvaOptions &opt)
MVA stages, bound to the caller's own MvaOptions.
SsaSerialSolution< T > solver_ssa_serial_analyzer(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt)
Port of solver_ssa_analyzer_serial.m plus the fork-join wrapper @@SolverSSA/runAnalyzer....
SsaSamplePath< T > ssa_sample_node(const qn::NetworkStruct< T > &sn, const SsaSerialRun< T > &r, std::size_t ind)
sample(node) and sampleAggr(node): the same trajectory, one node's block.
SsaProbReport solver_ssa_prob(const qn::NetworkStruct< T > &sn_arg, const SsaSerialOptions &opt_in)
The whole -a prob report over the model's DEFAULT INITIAL STATE, which is the state SolverCTMC's own ...
SsaSamplePath< T > ssa_sample_sys(const qn::NetworkStruct< T > &sn, const SsaSerialRun< T > &r)
sampleSys and sampleSysAggr: the trajectory itself.
SsaSolution solver_ssa(const qn::NetworkStruct< T > &sn, const SsaOptions &opt, std::vector< SsaCacheRatio > *cache=nullptr)
qn::NetworkStruct< T > sn_with_ssa_cache_split(const qn::NetworkStruct< T > &base, const std::vector< SsaCacheRatio > &cache)
@@SolverSSA/runAnalyzer itself: the engine the method selects, then the result assembly the reference...
void ssa_cdf_respt_refuse()
getCdfRespT: refused, and the refusal is the ANSWER rather than a gap.
solvers::CacheMetrics< T > cache_metrics_of_ssa(const qn::NetworkStruct< T > &sn, const std::vector< SsaCacheRatio > &cache)
CacheMetrics from the serial engine's cache write-back.
std::string sym_find_image()
const char *const SYM_DOCKER_IMAGE
Image serving the symbolic REST API.
UqSolution< T > solver_uq_run_analyzer(qn::Network< T > &net, const UqStageSolver< T > &stage, const UqOptions &opt=UqOptions())
UQ.runAnalyzer as a free call: expand, solve every design point, aggregate.
UqInterval< T > uq_interval_run(qn::Network< T > &net, const UqStageSolver< T > &stage, const UqOptions &opt=UqOptions())
getInterval from the model, running the ensemble ONLY when it is needed.
std::vector< PriorSite< T > > uq_detect_priors(const qn::NetworkStruct< T > &sn)
UQ.detectPriors: find every Prior, in node order and then class order.
UqStageSolver< T > uq_stage_solver(const UqStageOptions &o)
The stage solver named by o.solver.
std::string method_type(const std::string &solvername, const std::string &method)
Banner classification of a solution method.
Conservation laws of a layered queueing network, enumerated from its structure.
const char * arith_name(Arith a)
const std::vector< ApiEntry > & api_registry()
The registry is a function-local static, not a global object, so there is no static-initialization or...
Random queueing-network generation: the C++ twin of MATLAB @NetworkGenerator, the JAR jline....
Reader for the LINE model.json interchange (a Network model) into a qn::Network<T> built through the ...
Number-type abstraction for the templated API port.
PNML (ISO/IEC 15909-2) place/transition nets, read and written.
Coverage registry of the C++ port.
Ports of matlab/src/api/sn/sn_get_node_arvr_from_tput.m and sn_get_node_tput_from_tput....
Ports of matlab/src/api/sn/sn_get_state_aggr.m and sn_is_state_valid.m.
The SolverAUTO chooser: which solver a model is handed to.
The SolverBA class surface: @@SolverBA/runAnalyzer.m, listValidMethods, getBounds and getBoundsTable.
The CHAIN-level and SYSTEM-level views of a solved model.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Port of @@SolverCTMC/getCdfRespT.m and @@SolverCTMC/getCdfSysRespT.m: the exact distribution of the r...
The remaining @@SolverCTMC accessors: getGenerator / getInfGen, getStateSpace / getStateSpaceAggr and...
The mdd method of SolverCTMC: stationary analysis of a closed single-class network whose state space ...
The SolverCTMC probability family: solver_ctmc_joint, _jointaggr, _marg, _margaggr,...
Port of solver_ctmc_reward.m and the @@SolverCTMC reward surface (runRewardAnalyzer,...
Port of the @@SolverCTMC sampling surface: sample, sampleAggr, sampleSys, sampleSysAggr.
Port of @@SolverCTMC/getSensitivity and getSensitivityRanking: the parametric sensitivity of a steady...
Port of solver_ctmc_fcr_waitq.m: the reachability-built generator of a model whose finite capacity re...
The base-class fallback for a response-time CDF.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
Port of SolverJMT, the Java Modelling Tools client.
Port of SolverLDES, the discrete-event simulator, as its C++ client.
SolverLN: layered decomposition of a layered queueing network.
SolverLQNS: the layered model solved by the external lqns / lqsim binaries.
The SolverMAM class surface: @@SolverMAM/runAnalyzer.m and the gates around it.
The state-probability half of the SolverMVA class surface.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
Port of @SolverNC/getAvgBusyPeriod.m and of the Python-native SolverNC.getAvgBusyPeriod: the mean bus...
Port of @@SolverNC/getCdfRespT.m, and of its two aliases getSjrnT and sjrnT.
The cftp and cftp.approx methods of SolverNC: stationary analysis of a closed single-class product-fo...
The state-probability half of the SolverNC class surface: ports of solver_nc_marg....
The SolverNC class surface: @@SolverNC/runAnalyzer.m and the gates around it.
The NODE-indexed view of a station result, behind getAvgNodeTable.
Performance sensitivities with respect to service rates.
The SolverSSA queries that are not the average table: getProb, getProbAggr, getProbSys,...
SRVN input format (the LQNS .in / .lqn text grammar) for layered models, the C++ twin of python/line_...
The SolverSSA entry surface: a port of @@SolverSSA/runAnalyzer.m's method whitelist,...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
std::string method
'default', 'inap', 'inaprc', 'inapinf' or the vestigial 'exact'.
int iter_max
SolverOptions('AG') lowers this from the global 1000 to 100.
double tol
Convergence tolerance of the reversed-rate fixed point.
std::size_t max_states
Truncation level of an OPEN agent's queue-length dimension.
std::string nonmkv
options.config.nonmkv: 'bernstein' (default) runs sn_nonmarkov_toph, 'none' skips it; any other name ...
What a ranking resolved to, and what it had to skip to get there.
std::string method
The method the choice was gated on: "" for the default, "exact".
std::vector< AutoSolver > skipped
Slots that outranked solver and have no engine in this port.
std::vector< AutoEnv > retry
delegate.m's proposed list: the chosen ensemble, then every candidate in slot order (MVA,...
std::vector< AutoEnv > skipped
std::vector< AutoLayered > skipped
resolveMethodToken, minus the unqualified-algorithm-name arm.
std::string submethod
the method handed to the family, "default" when bare
std::string family
empty when is_intent
Port of SolverBA.getBounds: the {lower,upper} bracket of a family.
std::vector< std::vector< bool > > keep
getBoundsTable's row filter, (M x K): whether the (station, class) pair earns a row.
Matrix< T > Qupper
(M x K), all-NaN on a side the family lacks
options.config.qrf_params, the blocking tables the BAS and RS-RD arms need.
std::vector< std::vector< int > > MM1
(MR x M) extended order
std::vector< std::vector< int > > MM
(MR x 2) blocking order
std::vector< std::vector< int > > BB
(MR x M) blocking state
int MR
number of blocking configurations
int f
finite-capacity queue, 1-based as in the reference
std::vector< int > ZZ
(MR) blocked count per config
std::vector< int > F
(M) capacity; empty takes sn.cap
The options SolverBA reads.
int level
options.level: the hierarchy level of pbh/bjbh/cbh/sib.
std::string method
Bound method; default resolves to gb.upper in the runner.
Matrix< double > qrf_alpha
options.config.qrf_alpha, the (nstations x N) load-dependent scaling of the two load-dependent QRF ar...
One invocation: the argument vector, plus what a pipe would have carried.
bool collect_json
Collect the JSON documents as they are emitted.
int(*) interrupt(void *user)
Polled between analyses and at each document emission; non-zero aborts.
std::vector< std::string > argv
The argument vector WITHOUT argv[0], e.g.
std::string stdin_text
The model document, as if piped to the binary's stdin.
bool capture_stderr
Redirect fd 2 for the duration of the call into Response::diagnostics.
bool capture_stdout
Redirect fd 1 for the duration of the call into Response::text.
bool allow_server
Permit -p, which serves until its request budget is exhausted.
What one invocation produced.
std::string line_error_id
The cross-codebase identifier of error, LINE:<category>:<kind> or the unclassified LINE:Error (_kb/17...
int exit_code
The CLI's own exit status: 0 ok, 2 line::Error, 3 other.
std::string error_id
line::io::error_id's stable identifier for error.
std::vector< std::string > documents
Each JSON document the run emitted, in emission order, AS EMITTED.
std::string error
Empty on success; the what() of the exception otherwise.
std::string text
Everything written to fd 1, with the collected documents still in it.
std::string diagnostics
Everything written to fd 2: warnings, and the failure message.
A CTMC solve routed to whichever path the model's region rules require.
std::vector< T > parked
mean parked jobs per class; empty off the WAITQ path
The answer of @@SolverCTMC/getFirstPassTMoments.m.
std::vector< T > p
(nstates) probability of entering B (before C), one row per state
Matrix< T > mall
(nstates x nmax), one row per starting state
std::vector< std::size_t > source
resolved 0-based rows; empty = conditional stationary
std::vector< std::size_t > target
resolved 0-based rows
std::vector< std::size_t > taboo
resolved 0-based rows of C minus B; empty = none
std::vector< T > m
(nmax) moments for a passage started uniformly in A
The answer of @@SolverCTMC/getCdfFirstPassT.m: the [F(t), t] curve with its grid, density and resolve...
[infGen, eventFilt, ev] of @@SolverCTMC/getGenerator.m.
std::vector< std::vector< Matrix< T > > > preempt_filt
std::vector< Matrix< T > > filt
eventFilt: filt[a] holds only what synchronization sync[a] contributed, so sum_a filt[a] is the off-d...
std::vector< Sync< T > > sync
ev, the reference's sn.sync
std::vector< std::vector< Matrix< T > > > start_filt
The DERIVED START/PREEMPT filtrations, indexed [station-1][class-1].
Matrix< T > Q
the infinitesimal generator
What one mdd solve produces beside the means, i.e.
long long num_states
|S|, counted in the diagram without ever listing a state.
int iters
Coupled fixed-point sweeps performed.
bool no_aggregation
True certifies the answer is exact structurally; see the file header.
std::string encoding
Which local encoding was picked, "np" or "ps".
std::vector< std::size_t > level_sizes
|M_k| per paper level; their sum is what the diagram actually holds.
The SolverCTMC knobs this port honours.
bool force
options.force: downgrade the memory pre-gate's refusal to a warning.
std::vector< std::vector< std::size_t > > cutoff_mat
options.cutoff AS A (station x class) MATRIX, or empty.
double fau_delta
options.config.fau_delta: occupancy below which a state is dropped.
double cutoff
< 0 = not given
double timestep
options.timestep: the FIXED OUTPUT STEP of a transient analysis.
std::string nonmkv
options.config.nonmkv: 'bernstein' (default) runs sn_nonmarkov_toph, 'none' skips it; any other name ...
std::string transient_method
options.config.transient_method: "ode" (the default) integrates the forward equation,...
std::vector< CtmcRateSched > rate_sched
options.config.rate_sched: the TIME-INHOMOGENEOUS transient.
std::size_t ctmc_tv_ngrid
options.config.ctmc_tv_ngrid: uniform grid size of the rate_sched propagator.
double fau_epsilon
options.config.fau_epsilon: total probability mass the whole grid may discard under "fau".
bool keep_filtration
Keep the per-synchronization EVENT FILTRATION alongside Q.
One entry of options.config.rate_sched: the rate of (station, class) follows the piecewise-linear sch...
std::vector< double > rates
std::vector< double > tgrid
What the reward analyzer produces, per declared reward.
std::vector< Matrix< T > > V
V[r] is (Tmax+1 x nstates).
std::vector< T > t
iteration index / q
Matrix< T > state_space_aggr
the rows the reward saw
std::vector< std::string > names
One sampled trajectory of the chain.
std::vector< std::size_t > event
synchronization that fired to LEAVE it
std::vector< std::size_t > state
0-based index into chain.space
std::vector< T > t
time at which each state was ENTERED
The scalar parameter a sensitivity is taken with respect to.
std::function< void(NetworkStruct< T > &, double)> set
Apply theta to a COPY of the struct; the original is never mutated.
Everything one CTMC solve produces.
std::string warning
Set when the chain is a reducible mixture solved from an invented seed.
std::vector< std::size_t > cutoff
the per-class cutoff actually used
std::vector< T > pi
stationary distribution over chain.space
[stateSpace, localStateSpace] of @@SolverCTMC/getStateSpace.m.
std::vector< std::size_t > node_width
column width of each stateful node's block
std::vector< Matrix< T > > local
localStateSpace{f}: one matrix per stateful node, its DISTINCT local rows in first-appearance order.
Matrix< T > flat
the blocks concatenated, as MATLAB returns them
The time-dependent answer of one getTranProb* query.
Matrix< T > pit
(ntimes x nstates) occupancy over the solved chain
std::vector< T > t
The reference returns Pi_t = [t, pi_t], one matrix with time glued on as column 1.
Matrix< T > labels
(nstates x width) the state descriptor the query asked for
What one transient CTMC solve produces.
What the ENV entry reports: the environment-blended metrics, and the whole result of whichever coupli...
std::string method
What ran: meanfield, statevec, or the limit avg / dec.
EnvLimitSolution limit
Populated on the closed-form limit path (avg, dec, occupation); limit.occupation / limit....
EnvStatevecSolution< T > statevec
Populated on the state-vector path.
bool converged
True on the limit path: a closed form has converged by construction.
solvers::CacheMetrics< T > cache
The environment-blended cache surface, whichever coupling produced it.
int iterations
ZERO ON THE LIMIT PATH, and that is the answer rather than a gap: a limit reads the environment once ...
std::vector< double > occupation
occupation only (getStageOccupation): the expected time spent in each stage over the horizon,...
std::vector< double > occupation_weight
std::string method
The inter-stage coupling: meanfield is the reference's default.
std::vector< double > env_init
Method occupation: options.config.env_init, the initial probability of each stage.
std::string stage_solver
Which solver runs each FLAT stage: fluid (the fluid transient), ctmc (the enumerated chain),...
fluid::FluidOptions stage
Options handed to each stage solver.
double stage_cutoff
options.cutoff of a CTMC stage, read only when stage_solver is ctmc.
double timespan_end
options.timespan(2) of the inner solver: the transient horizon.
double env_timespan_start
Method occupation: SolverENV's OWN options.timespan, [t0, t1].
std::size_t tran_points
Points on a UNIFORM transient grid, used only where stage_grid declines to build one (a stage whose h...
std::string system_type
"bufferless" or "singlebuffer"
What the AoI gate found, when it matches.
The four outputs of @@SolverFLD/getJacobian.
std::string engine
sage or local, whichever produced J
std::vector< std::string > rhs
the drift, one expression per variable
std::vector< std::vector< std::string > > J
J[i][j] = d f_i / d x_j.
bool has_equilibria
the backend answered the equilibria request
std::vector< std::map< std::string, std::string > > equilibria
Nonnegative solutions of f(x) = 0 together with the conserved invariants (fluid_conservation_constrai...
std::vector< std::string > vars
state variable names
The transient the covariance equation produces, i.e.
Matrix< double > Sigma
state-level covariance, on range(D)
Matrix< double > QStd
per station and class queue-length variance
Controls, defaulting to SolverOptions('Fluid') in the reference.
double pstar
exponent of the 'pnorm' smoothing
double iter_tol
>0 stops early when the moved-mass ratio falls below it; 0 runs to iter_max, as the reference does
double tol
absolute and relative tolerance handed to the integrator
std::string fluid_boundary
options.config.fluid_boundary: how the Petri dae route reports a fluid place level held at a bound,...
std::string nonmkv
options.config.nonmkv: 'bernstein' (default) runs sn_nonmarkov_toph, 'none' skips it; any other name ...
std::size_t iter_max
cap on outer integrations
bool pstar_set
Opt in to the p-norm under matrix/default too, which is what options.config.pstar does in MATLAB,...
What the analyzer returns, in the same shape as the MVA solver's result.
bool has_moments
result.solverSpecific.moments: set only by minnormal and refined.
bool has_aoi
result.solverSpecific.aoiResults: set only by the AoI branch of mfq, where the age laws,...
FluidMomentReport moments
std::string warning
The reference's line_warning text, verbatim, empty when it did not warn.
std::vector< double > xvec
the converged fluid state
solvers::FjMetrics< double > fj
self.result.FJ, set by the fork-join arm (fluid_fork_join_run); empty otherwise.
The symbolic system, in whichever of the two forms the method implies.
Backend selection, mirroring options.config.symbolic and its timeout.
bool equilibria
The reference's nargout >= 4: ask the backend to solve f(x) = 0.
std::string backend
auto to search, a URL, an image name, or none to stay local.
A closed interval the generator samples from.
The simulation controls the JSIM header carries, MATLAB's JMTIO properties.
std::string log_path
model.getLogPath, the logPath attribute
std::string file_name
base name; the header echoes it plus .jsimg
double max_simulated_time
The per-class queue-length trajectory of one node, plus its event stream.
The options of one JMT solve, SolverOptions('JMT') restricted to what is read.
int replications
options.config.replications: the independent JSIM runs the transient ensemble of default averages ove...
std::string method
default | jsim | jmva | jmva.<alg>
bool keep
keep the scratch directory after the solve
double max_simulated_time
double samples
samples per measure; raised to 5000 below
What jmt_prob_aggr reports: the system probability and the per-station ones.
std::vector< bool > station_seen
the same, per station
bool sys_seen
whether the joint state occurred at all
double sys
P(the whole network is in the declared state).
std::vector< double > station
P(station i holds its declared per-class counts).
Transient averages over independent replications, on one time grid.
std::vector< std::vector< std::vector< double > > > UNt
std::size_t valid
Replications that produced a usable trajectory.
std::vector< std::vector< std::vector< double > > > TNt
std::vector< std::vector< std::vector< double > > > QNt
QNt[ist-1][r] over t.
The result of a JMT solve: the shared AvgResult plus what only JMT reports.
std::map< std::size_t, std::vector< T > > cache_hit_prob
Per Cache node (1-based node index), the per-class hit probability.
Matrix< T > ACI
confidence half-widths, empty when disabled
Matrix< T > DropRateNfcr
(nregions x nclasses) carried and lost rate
The system trajectory: one per-class block per station, on a common grid.
static Distrib exp_rate(const T &r)
static constexpr double FineTol
static constexpr double CoarseTol
The knobs of one LDES run.
double warmupfrac
–warmupfrac, only for tranfilter=fixed
bool verbose
echo the resolved command line before running it
std::string rest_url
Base URL of an LDES REST server (the imperialqore/ldes container).
long seed
–seed; -1 requests a random stream
bool slotted
–slotted, run on the slot lattice
std::string cimethod
–cimethod: obm | bm | spectral | none
double slot_length
–slotlength
int replications
–replications; 0 = not given (one path)
std::size_t events
0 = not given; overrides samples when set
std::string tranfilter
–tranfilter: mser5 | fixed | none
std::vector< double > init_sol
–initsol, the warm-start placement as a STATION-MAJOR vector [st0_cl0, st0_cl1, .....
int numthreads
–numthreads; 0 = not given
std::size_t samples
-s, service-completion budget
double timeout
–maxtime, a COOPERATIVE wall-clock budget the event loop polls.
bool has_timespan
true when [t0,t1] was set: a TRANSIENT run
One ldes-result document, parsed.
std::vector< std::string > fj_forks
Fork and Join node NAMES of each pair, ordered by fork node then join node; empty without a pair.
std::map< std::string, LdesCacheMetrics > cache_metrics
Per-cache metrics, keyed by the Cache NODE name.
Matrix< double > FJTput
getAvgFJ, FJQLen = FJTput * FJRespT
std::vector< std::vector< Matrix< double > > > QNt
[STATION][class] -> (npoints x 2), columns [value, time].
Matrix< double > XN
(1 x nclasses), per-class visits and system tput
std::vector< std::vector< std::vector< double > > > respTimeSamples
[station][class] -> the per-job response times the engine recorded.
Matrix< double > WeightNfcr
Matrix< double > DropRateNfcr
Matrix< double > histogram_space
The exact joint-state residence-time histogram (–export-histogram).
std::string stopping_reason
convergence | max_events | max_sim_events | max_time.
std::vector< std::vector< Matrix< double > > > UNt
std::vector< std::string > class_names
std::string engine
"native" or "jar": which runner produced these numbers.
long long total_simulated_events
Matrix< double > MemOccNfcr
std::vector< std::string > station_names
Matrix< double > DropRateJoin
quorum-Join sibling drops, Join rows only
std::vector< std::string > fj_joins
bool timed_out
True when the HARD subprocess bound fired, not the cooperative one.
Matrix< double > histogram_time
std::vector< double > t
the time vector, empty on a steady-state run
std::vector< std::vector< Matrix< double > > > TNt
The layered result: per element, the mean measures.
Matrix< double > WLN
Residence time per element, the ResidT column: the time an activity holds ITS HOST PROCESSOR per visi...
std::vector< std::vector< double > > entry_resp_samples
Every per-request ENTRY response time observed, one vector per entry in LOCAL index space (0....
double simulated_time
Length of the MEASURED span, from the end of warmup to the end of the run.
long long completions
Reference-task cycles completed in the measured span.
getSensitivityTable of the ensemble: the layer tables under a Layer column.
std::string method
The summary label: the common branch, or "mixed" when they differ.
The LQN-level answer, indexed by element 1..nidx.
std::vector< bool > defined_W
std::vector< bool > defined_U
std::string warning
A warning the analysis raised, verbatim, empty when it raised none (method='nlp' off a root).
std::vector< bool > defined_R
std::vector< bool > defined_Q
bool is_bound
True when the numbers are a BOUND (method = mw.upper / mw.lower) rather than the fixed point.
std::vector< bool > defined_T
The layered transient: one block per layer, plus how it was produced.
The intermediate model, and the second stage that flattens it.
std::vector< LqnElement > type
(nidx+1)
std::vector< bool > iscache
(tshift+ntasks+1)
std::vector< std::string > names
(nidx+1) declared name
std::vector< bool > isref
(tshift+ntasks+1)
Knobs of the wrapper, the subset of SolverOptions that reaches lqns.
The six measures, on the element index space, with a defined mask each.
std::vector< bool > defined_U
std::vector< bool > defined_W
std::vector< bool > defined_Q
std::vector< bool > defined_R
std::vector< bool > defined_T
SolverLQNS.defaultOptions on a flat Network plus the knobs the JMVA document carries.
int timeout
Seconds before a hung qnsolver is killed; not positive waits forever.
bool keep
options.keep: leave the scratch directory behind, to inspect what was sent.
std::string multiserver
options.config.multiserver.
std::string method
default, qns or qns.NAME; see qns_methods().
The options SolverMAM reads.
std::size_t mapmsg_quantization
options.config.mapmsg_quantization: the number of regimes the MAP/M/s+G reneging analyzer quantizes t...
int iter_max
SolverOptions('MAM') lowers this from the global 1000 to 100.
double timespan_start
options.timespan: the transient horizon.
std::string timescale
options.config.timescale: "auto", "discrete" or "continuous".
std::string nonmkv
options.config.nonmkv: 'bernstein' (default) runs sn_nonmarkov_toph, 'none' skips it; any other name ...
std::size_t fj_accuracy
options.config.fj_accuracy: the FJ_codes truncation C of the queue-length DIFFERENCE between the two ...
std::size_t cutoff
options.cutoff: the level truncation getProb / getProbMarg use for an OPEN model, where the queue len...
std::string fj_tmode
options.config.fj_tmode: which route computeT.m takes to the T matrix, 'NARE' (Riccati,...
std::size_t sync_phases
options.config.sync_phases: the synchronous phase expansion of the 'sync.faster' / 'sync....
double slotlength
options.config.slotlength: the slot in model time units.
The joint (level, phase) table getProb returns: rows levels, cols phases.
What getTranAvg returns: queue length, utilization and throughput curves.
Knobs of the level iteration in mdd_mcd.
double tol
Convergence tolerance on the level marginals.
int maxiter
Maximum coupled sweeps before the iteration is declared non-convergent.
Port of @@SolverMVA/getProbAggr.m: P(n1 jobs of class 1, n2 of class 2, ...) at station ist for the m...
The metrics getAvg returns, after filtering.
std::shared_ptr< qn::NetworkStruct< T > > refreshed_struct
The struct whose cache self-switch carries the CONVERGED hit/miss split, filled by the cacheqn branch...
Matrix< T > RN
response time, per visit
std::string warning
The reference's own warning text, verbatim, empty when it did not warn.
Matrix< T > UN
utilization
solvers::FjMetrics< T > fj
self.result.FJ: the fork-join section metrics FJQLen, FJRespT and FJTput, filled by the solvers the f...
std::vector< T > listcost
(h) mean storage cost held by each cache list, K_j = sum_i sigma_i pi_ij, filled only by the NC cache...
std::optional< double > lognormconst
@@SolverNC/getProbNormConstAggr, i.e.
std::optional< bool > converged
Whether the fixed point met its tolerance, empty when the handler reports none.
Matrix< T > WN
residence time, per job
std::string method
the method asked for
std::string actualmethod
the algorithm that ran
Matrix< T > QN
queue length
std::vector< T > CN
system response time per class
std::vector< T > XN
system throughput per class
solvers::CacheMetrics< T > cache
What the cache branches observed, EMPTY on a model with no Cache node and on every solver that does n...
Matrix< T > AN
arrival rate
A marginal distribution and its logarithm, over the states asked for.
The options SolverMVA reads.
std::string fork_join
options.config.fork_join: which fork-join arm the fixed point takes.
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
The response-time distributions, station by class.
std::vector< std::vector< Matrix< T > > > RD
std::string warning
Non-empty when the reference WARNS AND RETURNS EMPTY rather than computing: today only "applies only ...
std::vector< T > tset
the shared evaluation grid
The knobs of one perfect-sampling run.
std::size_t samples
Number of iid stationary draws, SolverOptions('NC').samples by default.
unsigned long seed
Stream seed, so a row is reproducible within this port.
What one cftp solve produces beside the means.
std::vector< long > horizon
(samples) per-draw coalescence horizon, or the mixing steps of M_A.
std::vector< std::vector< int > > distinct_states
The distinct sampled states, aligned with paggr.
What the marginal analyzers return: one probability per station.
std::vector< T > P
(M) probability that station i holds its given vector
The [Q,U,R,T,C,X,lG] of the reference, plus the algorithm that ran.
Controls, defaulting to SolverOptions('NC') in the reference.
double slotlength
options.config.slotlength, the slot length in model time units.
std::string multiserver
options.config.multiserver: how a finite multiserver station is represented.
std::string fork_join
options.config.fork_join: which fork-join arm the shared fixed point takes on a model with a Fork.
std::size_t samples
options.samples, read by the estimators
unsigned long seed
options.seed, read by the estimators
bool slotted
options.config.slotted.
std::string cdf_algorithm
options.config.algorithm for getCdfRespT: 'exact' selects pfqn_stdf, 'rd' the heuristic pfqn_stdf_heu...
double iter_tol
options.iter_tol, the eta stopping test
What State.toMarginal returns for one station and one state row.
std::vector< T > nir
jobs per class
One network state: the per-stateful-node local rows it is composed of.
std::vector< std::vector< T > > local
local[isf] is that node's state row
Everything qsys_bmapm1 returns, mirroring the MATLAB result struct.
T q
uniformization constant actually used
T drift
stable iff strictly negative
T pi0
probability the system is empty
Matrix< T > levelProb
level probabilities, row n = pi_n
T lambda
mean arrival rate, theta (sum_k k D_k) e
std::vector< T > theta
stationary vector of the BMAP phase process
T rho
offered load lambda/mu
double decayRate
measured pi_(n+1)/pi_n; NaN when unmeasurable
std::vector< T > alpha
stationary vector of A
The name-value contract of getSensitivityTable.
bool simulation
True when the callback is a simulator, which widens the default step.
double step
Relative step of the rate perturbation; negative selects the default.
std::string scheme
forward | central
std::string method
auto | exact | fd
One (station, class) row of the table.
What the table carries, plus the branch that produced it.
std::vector< SensRow< T > > rows
std::string method
"exact" or "fd", the branch actually taken
Every Cache node of the model, in node order; empty on a model with none.
std::vector< CacheNodeMetrics< T > > caches
One Cache node's measured behaviour.
std::vector< T > delayedhitqlen
(n) mean secondary requests waiting on the in-flight fetch of each item, and the same including the r...
std::vector< double > itemcap
(h) capacity of each list
std::vector< double > itemsize
(n) storage cost per item, EMPTY without setItemSizes
std::vector< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
std::vector< T > delayedprob
(K) delayed-hit fraction, EMPTY off a retrieval system
Matrix< T > hitproblist
(K x h) per-list hit fraction, EMPTY = not computed
std::size_t node
1-based node index of the Cache
std::vector< T > latency
(K) expected latency per read request (hits wait 0), EMPTY = not computed
std::vector< T > missprob
(K)
std::vector< T > listcost
(h) mean storage cost held by each list
Matrix< T > itemprob
(n x h+1), column 0 = miss; EMPTY = not computed
std::vector< T > delayedhitqlenfull
std::string name
The Cache node's NAME, which is what a cross-language payload must key on.
The station- or node-level table aggregated by chain.
Matrix< T > TN
(rows x nchains), rows = stations or nodes
One options.config key; an empty placement means "not implemented there".
const char * values
comma-separated admissible values; empty when any string is taken
self.result.FJ: one row per Fork/Join pair, one column per class.
std::vector< std::size_t > forks
1-based fork node of each pair
Matrix< T > Tput
FJTput, P x R.
bool empty() const
No pair to report: a model without Fork/Join, or a solver that does not compute them.
Matrix< T > QLen
FJQLen, P x R.
std::size_t npairs() const
std::vector< std::size_t > joins
1-based join node of each pair
Matrix< T > RespT
FJRespT, P x R.
The caller-facing map_env knobs, options.config.map_env and friends.
std::size_t max_stages
0 = the transform's own default cap
The station table scattered to the NODE index space, plus the two flow columns the reference recomput...
@@NetworkSolver/getAvgSys: one response time and one throughput per chain.
std::vector< T > XN
(nchains) system throughput at the reference station
std::vector< T > CN
(nchains) system response time, i.e. the cycle time
Controls, defaulting to SolverOptions('SSA') in the reference.
std::size_t samples
Reaction firings to simulate; options.samples in the reference.
double warmupfrac
options.config.warmupfrac: the leading fraction of the path discarded before the means are taken.
std::string method
default and nrm both select the Next Reaction Method here.
unsigned long seed
options.seed; LINE's own default is 23000.
double cutoff
options.cutoff: the bound on an open class's jobs at a station, which the serial and replicated engin...
The four probabilities -a prob reports, over one requested state.
SsaProbResult sys_aggr
getProbSys, getProbSysAggr
std::vector< SsaProbResult > aggr
getProb, getProbAggr, per station
std::vector< SsaProbResult > marg
One trajectory, in the shape the reference's sampleSys returns it.
std::vector< double > t
the event times, increasing
Matrix< T > aggr
the same, as per-(stateful, class) counts
std::vector< std::size_t > event
which synchronization fired
Matrix< T > state
per event: the state OCCUPIED until then
The serial engine's knobs: SsaOptions plus the three the serial path reads and the NRM has no use for...
The serial analyzer's return: the metric table, the path, and the stream.
std::shared_ptr< const qn::NetworkStruct< T > > solved_sn
The struct the path was simulated on when it differs from the caller's (ctmc_mapc widens a multiserve...
What the analyzer returns, in the same shape as the MVA and fluid results.
double simulated_time
Simulated time the metrics are averaged over; the reference's totalTime.
std::size_t samples
Reaction firings actually performed.
std::string method
The concrete algorithm, as the reference's method.
UQ.getInterval: the RANGE of every metric over the support of the Priors.
std::string method
mvainterval or sampled.
bool exact
True when the interval is the attained hull rather than a sampled range.
Matrix< T > Qlo
(nstations x nclasses) lower and upper endpoints of each metric.
std::string why
On the sampled path, the condition that disqualified the exact one.
T Xlo
System throughput and total response time; the EXACT path only.
UQ.defaultOptions plus the stream the Monte Carlo design draws from.
std::string method
default | dd | quad | mc, MATLAB UQ.listValidMethods.
std::size_t samples
Nodes per continuous Prior, or design points under mc; the reference's options.samples,...
unsigned long seed
The Monte Carlo stream; unread by a quadrature design, which draws nothing.
What solver_uq_run_analyzer returns.
std::vector< T > weights
The design weights, summing to 1.
std::string method
The RESOLVED discretization method: quad or mc.
std::vector< mva::AvgResult< T > > points
The result at each design point, in design order.
std::vector< PriorSite< T > > sites
Where the Priors were found.
std::vector< UqDesignPoint< T > > design
The alternatives each point substituted, one per site.
mva::AvgResult< T > avg
The prior-weighted expectation of every metric, (nstations x nclasses).
The inner solver's knobs, carried through untranslated.
double tol
< 0 = not given
double cutoff
ctmc open-population cutoff; < 0 = not given
std::string solver
mva | nc | mam | ba | ctmc | fluid | ssa.
Resolves the symbolic backend to use, and owns the container that serves it.
The stage solver of SolverUQ, named rather than passed.
Minimal RFC 6455 WebSocket server, enough to serve LineWebSocketServer's protocol.