92const char* kVersion =
"0.1.0";
106bool g_json_output =
false;
130 double iter_tol = -1.0;
138 long long max_states = -1;
143 std::string multiserver;
148 std::string fork_join;
151 std::size_t samples = 0;
152 unsigned long seed = 0;
153 double cutoff = -1.0;
159 std::vector<std::vector<std::size_t>> cutoff_mat;
161 bool has_cutoff()
const {
return cutoff >= 0.0 || !cutoff_mat.empty(); }
173 std::string fj_tmode;
180 std::string timescale;
187 double mdd_tol = -1.0;
198 std::vector<std::size_t> busy_orders;
199 std::vector<std::size_t> busy_subnet;
203 std::string qrf_params;
204 std::string qrf_alpha;
208 double t0 = 0.0, t1 = -1.0;
209 std::size_t node = 0;
216 std::size_t jobclass = 0;
217 std::vector<long> marg_states;
223 double warmupfrac = -1.0;
232 std::string notation;
239 std::string cdf_algorithm;
245 std::string passage_from;
246 std::string passage_into;
248 std::string passage_method;
251 std::size_t passage_orders = 0;
261 std::string method_perm =
"exact";
269 std::string symbolic;
270 bool equilibria =
false;
274 bool no_interlocking =
false;
276 std::string layer_solver;
286 std::string stage_solver;
289 std::string ln_transient;
290 std::string ln_transient_channels;
295 std::string sens_method;
296 std::string sens_scheme;
297 double sens_step = -1.0;
301 std::string uq_solver;
307 std::size_t tran_points = 0;
314 bool verbose =
false;
316 std::string remote_url;
317 int timeout_seconds = 0;
326 std::string ldes_tranfilter;
327 double ldes_warmupfrac = -1.0;
328 std::string ldes_cimethod;
329 bool ldes_cnvgon =
false;
330 double ldes_cnvgtol = -1.0;
331 bool ldes_slotted =
false;
332 double ldes_slotlength = -1.0;
339 bool slotted =
false;
340 double slotlength = -1.0;
341 int ldes_replications = 0;
342 int ldes_numthreads = 0;
343 double ldes_maxtime = -1.0;
344 std::vector<double> ldes_initsol;
345 std::string ldes_rest_url;
358 std::vector<long> state;
367 std::size_t events = 0;
375 double timestep = -1.0;
387 std::string transient_method;
394 double fau_epsilon = -1.0;
395 double fau_delta = -1.0;
402 std::vector<double> percentiles;
412 std::string verbosity;
420 std::string reward_name;
432std::vector<std::vector<std::size_t>> parse_cutoff_matrix(
const std::string& s) {
433 std::vector<std::vector<std::size_t>> out;
434 std::string::size_type pos = 0;
435 while (pos <= s.size()) {
436 const std::string::size_type semi = s.find(
';', pos);
437 const std::string row = s.substr(pos, semi == std::string::npos ? std::string::npos
439 std::vector<std::size_t> cells;
440 std::string::size_type cp = 0;
441 while (cp <= row.size()) {
442 const std::string::size_type comma = row.find(
',', cp);
443 const std::string cell = row.substr(cp, comma == std::string::npos ? std::string::npos
445 if (cell.empty())
return std::vector<std::vector<std::size_t>>();
446 for (std::string::size_type i = 0; i < cell.size(); ++i)
447 if (!std::isdigit(
static_cast<unsigned char>(cell[i])))
448 return std::vector<std::vector<std::size_t>>();
449 cells.push_back(
static_cast<std::size_t
>(std::atol(cell.c_str())));
450 if (comma == std::string::npos)
break;
453 if (cells.empty())
return std::vector<std::vector<std::size_t>>();
454 if (!out.empty() && cells.size() != out[0].size())
455 return std::vector<std::vector<std::size_t>>();
456 out.push_back(cells);
457 if (semi == std::string::npos)
break;
472const std::string& stdin_model_text() {
473 static std::string buf;
474 static bool loaded =
false;
476 buf.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
490bool g_jsim_input =
false;
500bool g_pnml_input =
false;
509std::string g_verbosity =
"standard";
524 if (g_verbosity ==
"silent")
return;
528 if (!net.
raw_struct().priorities_ignored())
return;
530 "Warning: Priority classes are specified but no priority-aware scheduling "
531 "policy (PSPRIO, DPSPRIO, GPSPRIO, HOL, FCFSPRIO, FCFSPRPRIO, FCFSPIPRIO, "
532 "LCFSPRIO, LCFSPRPRIO, LCFSPIPRIO, SRPTPRIO) is used in the model. "
533 "Priorities will be ignored.\n");
544 warn_priorities_ignored(net);
549 std::remove(tmp.c_str());
550 warn_priorities_ignored(net);
560 warn_priorities_ignored(net);
565 std::remove(tmp.c_str());
566 warn_priorities_ignored(net);
571 warn_priorities_ignored(net);
574 std::istringstream in(stdin_model_text());
575 line::io::detail::json root;
578 warn_priorities_ignored(net);
586 std::istringstream in(stdin_model_text());
587 line::io::detail::json root;
594 double q, u, r, w, a, t;
626void emit_avg_table_named(
const std::vector<std::string>& stations,
627 const std::vector<std::string>& classes,
const char* arith,
631 for (
const char* key : {
"Station",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
633 rows[key] = line::reg::Json::array();
634 rows[
"type"] =
"AvgTable";
637 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen",
638 "Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
639 for (std::size_t i = 0; i < stations.size(); ++i) {
640 for (std::size_t c = 0; c < classes.size(); ++c) {
641 const AvgRow v =
get(i, c);
642 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)
644 if (!g_json_output) {
645 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
646 stations[i].c_str(), classes[c].c_str(), v.q, v.u, v.r, v.w, v.a, v.t);
653 rows[
"Station"].push_back(stations[i]);
654 rows[
"JobClass"].push_back(classes[c]);
655 rows[
"QLen"].push_back(v.q);
656 rows[
"Util"].push_back(v.u);
657 rows[
"RespT"].push_back(v.r);
658 rows[
"ResidT"].push_back(v.w);
659 rows[
"ArvR"].push_back(v.a);
660 rows[
"Tput"].push_back(v.t);
667 for (line::reg::Json::const_iterator it = extra.begin(); it != extra.end(); ++it)
668 rows[it.key()] = it.value();
671 out[
"arith"] = arith;
672 out[
"method"] = method;
677 for (line::reg::Json::const_iterator it = envelope.begin(); it != envelope.end(); ++it)
678 out[it.key()] = it.value();
679 std::printf(
"%s\n", out.dump().c_str());
692template <
class T,
class Get>
696 std::vector<std::string> stations, classes;
699 for (std::size_t i = 0; i < sn.
nstations; ++i) stations.push_back(sn.
stations[i].name);
700 for (std::size_t c = 0; c < sn.
nclasses; ++c) classes.push_back(sn.
classes[c].name);
727void emit_analysis(
const char* key,
const line::reg::Json& payload,
const std::string& method,
736 for (line::reg::Json::const_iterator it = extra.begin(); it != extra.end(); ++it)
737 body[it.key()] = it.value();
741 if (!method.empty()) out[
"method"] = method;
742 std::printf(
"%s\n", out.dump().c_str());
749 for (std::size_t i = 0; i < M.
rows(); ++i) {
751 for (std::size_t j = 0; j < M.
cols(); ++j)
769 for (std::size_t i = 0; i < v.size(); ++i) a.push_back(v[i]);
794 for (std::size_t c = 0; c < cache.
caches.size(); ++c) {
848 std::fprintf(stderr,
"Warning: %s\n", r.
warning.c_str());
856 for (std::size_t j = 0; j < r.
listcost.size(); ++j)
858 extra[
"ListCost"] = lc;
866 if (extra_in.is_object())
867 for (line::reg::Json::const_iterator it = extra_in.begin(); it != extra_in.end(); ++it)
868 extra[it.key()] = it.value();
872 if (!r.
cache.empty()) extra[
"Cache"] = cache_extra_json<T>(r.
cache);
879 envelope[
"iter"] = r.
iter;
886 emit_avg_table<T>(sn, r.
actualmethod, [&](std::size_t i, std::size_t c) {
888 row.q = line::num_traits<T>::to_double(r.QN(i, c));
889 row.u = line::num_traits<T>::to_double(r.UN(i, c));
890 row.r = line::num_traits<T>::to_double(r.RN(i, c));
891 row.w = line::num_traits<T>::to_double(r.WN(i, c));
892 row.a = line::num_traits<T>::to_double(r.AN(i, c));
893 row.t = line::num_traits<T>::to_double(r.TN(i, c));
899int solve_model_mva(
const std::string& file,
const Knobs& k) {
902 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
903 if (k.tol >= 0.0) opt.
tol = k.tol;
904 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
905 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
906 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
907 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
916 print_avg_table<T>(sn, r);
930int solve_model_jmt(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
935 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
936 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
937 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
948 if (analysis ==
"prob") {
949 std::size_t target = 0;
951 if (k.node <= sn.
nodes.size()) target = sn.
nodes[k.node - 1].station;
954 "--node " + std::to_string(k.node) +
955 " is not a station, so it holds no per-class job count to ask about");
958 sn, o, target, std::vector<double>(k.state.begin(), k.state.end()));
961 p[
"type"] =
"ProbAggr";
963 p[
"ProbSysAggr"] = r.
sys;
969 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
970 sv = line::reg::Json::array();
971 for (std::size_t i = 0; i < sn.
nstations; ++i) {
983 emit_analysis<double>(
"prob", p, std::string(
"jsim"));
986 std::printf(
"SolverJMT arith=double method=jsim\n");
987 std::printf(
"ProbSysAggr %.10g%s\n", r.
sys, r.
sys_seen ?
"" :
" (state never observed)");
988 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
989 for (std::size_t i = 0; i < sn.
nstations; ++i)
990 std::printf(
"%-16s %14.10g%s\n", sn.
stations[i].name.c_str(), r.
station[i],
995 if (analysis ==
"cdf" || analysis ==
"trancdf" || analysis ==
"trancdfpasst") {
999 const std::map<std::pair<std::size_t, std::size_t>,
1000 std::vector<std::pair<double, double> > >
1003 for (std::map<std::pair<std::size_t, std::size_t>,
1004 std::vector<std::pair<double, double> > >::const_iterator it = rd.begin();
1005 it != rd.end(); ++it) {
1007 for (std::size_t i = 0; i < it->second.size(); ++i) {
1009 row.push_back(it->second[i].first);
1010 row.push_back(it->second[i].second);
1011 rows.push_back(row);
1013 const std::string key =
1015 sn.
classes[it->first.second - 1].name;
1018 std::printf(
"%-24s %8zu points respT(max)=%.6g\n", key.c_str(),
1019 it->second.size(), it->second.back().second);
1021 if (g_json_output) {
1024 std::printf(
"%s\n", out.dump(2).c_str());
1030 std::printf(
"SolverJMT arith=double method=%s\n", r.
avg.actualmethod.c_str());
1039 &table.
TN, &table.
AN, &table.
WN};
1042 for (
int m = 0; m < 6; ++m) {
1044 for (std::size_t i = 0; i < M; ++i)
1045 for (std::size_t c = 0; c < K; ++c) t(i, c) = (*src[m])(i, c);
1058 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array();
1059 line::reg::Json rt = line::reg::Json::array(), w = line::reg::Json::array();
1060 line::reg::Json a = line::reg::Json::array(), t = line::reg::Json::array();
1061 for (std::size_t f = 0; f < sn.
regions.size() && M + f < r.
avg.QN.rows(); ++f) {
1062 names.push_back(f < sn.
regions.size() && !sn.
regions[f].name.empty()
1064 :
"FCR" + std::to_string(f + 1));
1065 for (std::size_t c = 0; c < K; ++c) {
1066 q.push_back(r.
avg.QN(M + f, c));
1067 u.push_back(r.
avg.UN(M + f, c));
1068 rt.push_back(r.
avg.RN(M + f, c));
1069 w.push_back(r.
avg.WN(M + f, c));
1070 a.push_back(r.
avg.AN(M + f, c));
1071 t.push_back(r.
avg.TN(M + f, c));
1074 fcr_extra[
"Region"] = names;
1075 fcr_extra[
"QLen"] = q;
1076 fcr_extra[
"Util"] = u;
1077 fcr_extra[
"RespT"] = rt;
1078 fcr_extra[
"ResidT"] = w;
1079 fcr_extra[
"ArvR"] = a;
1080 fcr_extra[
"Tput"] = t;
1083 if (!fcr_extra.empty()) avg_extra[
"FCR"] = fcr_extra;
1084 print_avg_table<double>(sn, table, avg_extra);
1086 if (!g_json_output && r.
TNfcr.rows() > 0) {
1087 std::printf(
"%-16s %-14s %12s %12s\n",
"Region",
"JobClass",
"Tput",
"DropRate");
1088 for (std::size_t f = 0; f < r.
TNfcr.rows(); ++f)
1089 for (std::size_t c = 0; c < sn.
nclasses; ++c)
1094 std::printf(
"%-16s %-14s %12.6g %12.6g\n",
1097 :
"FCR" + std::to_string(f + 1))
1102 for (std::map<std::size_t, std::vector<double> >::const_iterator it =
1105 for (std::size_t c = 0; c < it->second.size(); ++c)
1106 std::printf(
"%-16s %-14s hitProb=%12.6g\n", sn.
nodes[it->first - 1].name.c_str(),
1107 sn.
classes[c].name.c_str(), it->second[c]);
1120int solve_model_nc(
const std::string& file,
const Knobs& k) {
1123 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1124 if (k.tol >= 0.0) opt.
tol = k.tol;
1125 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
1126 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1127 if (k.samples) opt.
samples = k.samples;
1128 if (k.seed) opt.
seed = k.seed;
1129 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1130 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
1131 if (k.slotted) opt.
slotted =
true;
1132 if (k.slotlength > 0.0) {
1143 std::printf(
"SolverNC arith=%s method=%s type=%s lognormconst=%.10g\n",
1147 print_avg_table<T>(sn, r);
1161int solve_model_mam(
const std::string& file,
const Knobs& k) {
1164 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1165 if (k.tol >= 0.0) opt.
tol = k.tol;
1166 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1173 print_avg_table<T>(sn, r);
1198 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1199 if (k.tol >= 0.0) opt.
tol = k.tol;
1200 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1201 if (k.max_states > 0) opt.
max_states =
static_cast<std::size_t
>(k.max_states);
1205int solve_model_ag(
const std::string& file,
const Knobs& k) {
1208 apply_ag_knobs(k, opt);
1215 print_avg_table<T>(sn, r);
1230 if (k.node)
return k.node;
1231 for (std::size_t a = 0; a < sn.
nof_nodes(); ++a)
1232 if (sn.
nodes[a].nodetype == line::qn::NodeType::Queue)
return a + 1;
1234 "the MAM per-node analyses report a queue's internals and this model has no Queue node");
1240 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1241 if (k.tol >= 0.0) opt.
tol = k.tol;
1242 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1243 if (k.cutoff >= 0.0) opt.
cutoff =
static_cast<std::size_t
>(k.cutoff);
1244 if (k.fj_accuracy > 0) opt.
fj_accuracy =
static_cast<std::size_t
>(k.fj_accuracy);
1245 if (!k.fj_tmode.empty()) opt.
fj_tmode = k.fj_tmode;
1246 if (!k.timescale.empty()) opt.
timescale = k.timescale;
1247 if (k.slotlength > 0.0) opt.
slotlength = k.slotlength;
1269int solve_model_mam_prob(
const std::string& file,
const Knobs& k) {
1276 const std::size_t node = mam_query_node<T>(sn, k);
1277 const std::size_t ist = sn.
nodes[node - 1].station;
1279 std::vector<std::vector<T> > marg(sn.
nclasses);
1280 for (std::size_t r = 0; r < sn.
nclasses; ++r)
1283 if (g_json_output) {
1285 p[
"type"] =
"ProbTable";
1287 p[
"node"] = node - 1;
1288 p[
"Node"] = sn.
nodes[node - 1].name;
1289 p[
"levels"] = P.
P.rows();
1290 p[
"phases"] = P.
P.cols();
1292 for (std::size_t n = 0; n < P.
P.rows(); ++n) {
1294 for (std::size_t j = 0; j < P.
P.cols(); ++j)
1296 joint.push_back(row);
1300 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
1302 e[
"JobClass"] = sn.
classes[r].name;
1304 e[
"P"] = vector_json(marg[r]);
1311 std::printf(
"SolverMAM arith=%s method=%s node=%s levels=%zu phases=%zu\n",
1313 sn.
nodes[node - 1].name.c_str(), P.
P.rows(), P.
P.cols());
1314 std::printf(
"%-8s %-8s %16s\n",
"Level",
"Phase",
"Prob");
1315 for (std::size_t n = 0; n < P.
P.rows(); ++n)
1316 for (std::size_t j = 0; j < P.
P.cols(); ++j)
1317 std::printf(
"%-8zu %-8zu %16.10g\n", n, j + 1,
1319 std::printf(
"%-14s %-8s %16s\n",
"JobClass",
"Jobs",
"Prob");
1320 for (std::size_t r = 0; r < sn.
nclasses; ++r)
1321 for (std::size_t n = 0; n < marg[r].size(); ++n)
1322 std::printf(
"%-14s %-8zu %16.10g\n", sn.
classes[r].name.c_str(), n,
1331std::vector<double> percentile_levels(
const Knobs& k) {
1332 if (!k.percentiles.empty())
return k.percentiles;
1333 std::vector<double> pcts;
1334 pcts.push_back(0.50);
1335 pcts.push_back(0.90);
1336 pcts.push_back(0.95);
1337 pcts.push_back(0.99);
1362int solve_model_mam_cdf(
const std::string& file,
const Knobs& k,
const char* key,
1367 const std::vector<double> pcts = percentile_levels(k);
1370 const std::vector<std::vector<T> > perc =
1372 if (g_json_output) {
1374 p[
"type"] =
"PerctRespT";
1376 p[
"source"] =
"fjcodes";
1378 for (std::size_t r = 0; r < perc.size(); ++r) {
1380 e[
"JobClass"] = sn.
classes[r].name;
1382 e[
"percentileLevels"] = pcts;
1383 e[
"percentiles"] = vector_json(perc[r]);
1387 emit_analysis<T>(key, p, opt.
method);
1392 std::printf(
"# the response-time CDF has no fork-join route in the reference; these are "
1393 "the FJ_codes percentiles getPerctRespT reads\n");
1394 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
1395 for (std::size_t r = 0; r < perc.size(); ++r)
1396 for (std::size_t j = 0; j < perc[r].size(); ++j)
1397 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
1403 std::vector<std::vector<T> > perc;
1404 for (std::size_t r = 0; r < rd.size(); ++r)
1407 if (g_json_output) {
1412 for (std::size_t r = 0; r < rd.size(); ++r) {
1416 if (rd[r].X.empty())
continue;
1418 e[
"JobClass"] = sn.
classes[r].name;
1420 e[
"t"] = vector_json(rd[r].X);
1421 e[
"F"] = vector_json(rd[r].F);
1422 e[
"percentileLevels"] = pcts;
1423 e[
"percentiles"] = vector_json(perc[r]);
1427 emit_analysis<T>(key, p, opt.
method);
1432 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Time",
"F(t)");
1433 for (std::size_t r = 0; r < rd.size(); ++r)
1434 for (std::size_t j = 0; j < rd[r].X.size(); ++j)
1435 std::printf(
"%-14s %14.8g %14.10g\n", sn.
classes[r].name.c_str(),
1438 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
1439 for (std::size_t r = 0; r < perc.size(); ++r)
1440 for (std::size_t j = 0; j < perc[r].size(); ++j)
1441 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
1458int solve_model_mam_perct(
const std::string& file,
const Knobs& k) {
1462 const std::vector<double> pcts = percentile_levels(k);
1464 std::vector<std::vector<T> > perc;
1469 const std::vector<line::mam::RespTCdf<T> > rd =
1471 for (std::size_t r = 0; r < rd.size(); ++r)
1475 if (g_json_output) {
1477 p[
"type"] =
"PerctRespT";
1479 p[
"source"] = fj ?
"fjcodes" :
"cdf";
1481 for (std::size_t r = 0; r < perc.size(); ++r) {
1482 if (perc[r].empty())
continue;
1484 e[
"JobClass"] = sn.
classes[r].name;
1486 e[
"percentileLevels"] = pcts;
1487 e[
"percentiles"] = vector_json(perc[r]);
1491 emit_analysis<T>(
"perct", p, opt.
method);
1494 std::printf(
"SolverMAM arith=%s method=%s classes=%zu source=%s\n",
1496 fj ?
"fjcodes" :
"cdf");
1497 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
1498 for (std::size_t r = 0; r < perc.size(); ++r)
1499 for (std::size_t j = 0; j < perc[r].size(); ++j)
1500 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
1515int solve_model_mam_tran(
const std::string& file,
const Knobs& k) {
1521 if (g_json_output) {
1523 p[
"type"] =
"TranAvgTable";
1528 for (std::size_t i = 0; i < tr.Qt.size(); ++i)
1529 for (std::size_t r = 0; r < tr.Qt[i].size(); ++r) {
1530 if (tr.Qt[i][r].times.empty())
continue;
1532 e[
"Station"] = sn.
stations[i].name;
1533 e[
"JobClass"] = sn.
classes[r].name;
1536 e[
"t"] = tr.Qt[i][r].times;
1537 e[
"QLen"] = vector_json(tr.Qt[i][r].values);
1538 e[
"Util"] = vector_json(tr.Ut[i][r].values);
1539 e[
"Tput"] = vector_json(tr.Tt[i][r].values);
1543 emit_analysis<T>(
"tran", p,
"ldqbd");
1548 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
1550 for (std::size_t i = 0; i < tr.Qt.size(); ++i)
1551 for (std::size_t r = 0; r < tr.Qt[i].size(); ++r)
1552 for (std::size_t j = 0; j < tr.Qt[i][r].times.size(); ++j)
1553 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
1555 tr.Qt[i][r].times[j],
1573int solve_model_mam_internals(
const std::string& file,
const Knobs& k) {
1580 if (g_json_output) {
1582 p[
"type"] =
"MAMResult";
1586 p[
"uniformization"] = q;
1596 p[
"theta"] = vector_json(r.
theta);
1597 p[
"alpha"] = vector_json(r.
alpha);
1599 for (std::size_t n = 0; n < r.
levelProb.rows(); ++n) {
1601 for (std::size_t j = 0; j < r.
levelProb.cols(); ++j)
1605 p[
"levelProb"] = lv;
1606 emit_analysis<T>(
"internals", p, std::string());
1610 std::printf(
"lambda=%.10g rho=%.10g q=%.10g drift=%.10g decayRate=%.10g\n",
1613 std::printf(
"pi0=%.10g QLen=%.10g Util=%.10g Tput=%.10g truncLevel=%zu truncError=%.3g "
1620 std::printf(
"%-8s %16s\n",
"Level",
"Prob");
1621 for (std::size_t n = 0; n < r.
levelProb.rows(); ++n) {
1623 for (std::size_t j = 0; j < r.
levelProb.cols(); ++j)
1625 std::printf(
"%-8zu %16.10g\n", n, s);
1640line::reg::Json read_json_arg(
const std::string& spec,
const char* flag) {
1641 std::string text = spec;
1642 const std::size_t at = spec.find_first_not_of(
" \t\r\n");
1643 if (at == std::string::npos || (spec[at] !=
'{' && spec[at] !=
'[')) {
1644 std::ifstream in(spec.c_str());
1646 throw line::InputError(std::string(flag) +
" is neither inline JSON nor a readable "
1647 "file (got '" + spec +
"')");
1648 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
1651 return line::reg::Json::parse(text);
1652 }
catch (
const line::reg::Json::parse_error& e) {
1653 throw line::InputError(std::string(
"malformed ") + flag +
" JSON: " + e.what());
1658std::vector<std::vector<int> > json_int_table(
const line::reg::Json& j,
const char* name) {
1660 throw line::InputError(std::string(
"--qrf-params ") + name +
" must be an array of rows");
1661 std::vector<std::vector<int> > out;
1662 for (std::size_t m = 0; m < j.size(); ++m) {
1663 if (!j[m].is_array())
1664 throw line::InputError(std::string(
"--qrf-params ") + name +
" must be an array of "
1666 std::vector<int> row;
1667 for (std::size_t c = 0; c < j[m].size(); ++c) row.push_back(j[m][c].get<
int>());
1683 if (!j.is_object())
throw line::InputError(
"--qrf-params must be a JSON object");
1684 const char* required[] = {
"f",
"MR",
"BB",
"MM",
"MM1",
"ZZ"};
1685 std::string missing;
1686 for (std::size_t i = 0; i < 6; ++i)
1687 if (!j.contains(required[i]))
1688 missing += (missing.empty() ?
"" :
", ") + std::string(required[i]);
1689 if (!missing.empty())
1691 "; required are f, MR, BB, MM, MM1, ZZ (F is optional, ZM is "
1692 "derived from ZZ)");
1695 p.
f = j[
"f"].get<
int>();
1696 p.
MR = j[
"MR"].get<
int>();
1697 p.
BB = json_int_table(j[
"BB"],
"BB");
1698 p.
MM = json_int_table(j[
"MM"],
"MM");
1699 p.
MM1 = json_int_table(j[
"MM1"],
"MM1");
1700 if (!j[
"ZZ"].is_array())
throw line::InputError(
"--qrf-params ZZ must be an array");
1701 for (std::size_t i = 0; i < j[
"ZZ"].size(); ++i) p.
ZZ.push_back(j[
"ZZ"][i].get<
int>());
1702 if (j.contains(
"F"))
1703 for (std::size_t i = 0; i < j[
"F"].size(); ++i) p.
F.push_back(j[
"F"][i].get<
int>());
1710 if (!j.is_array() || j.empty() || !j[0].is_array())
1711 throw line::InputError(
"--qrf-alpha must be a JSON array of rows, one per station");
1713 for (std::size_t i = 0; i < j.size(); ++i) {
1714 if (!j[i].is_array() || j[i].size() != j[0].size())
1716 for (std::size_t n = 0; n < j[i].size(); ++n) a(i, n) = j[i][n].get<
double>();
1723 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1724 if (k.level > 0) opt.
level = k.level;
1725 if (!k.qrf_params.empty()) decode_qrf_params(k.qrf_params, opt);
1726 if (!k.qrf_alpha.empty()) decode_qrf_alpha(k.qrf_alpha, opt);
1730int solve_model_ba(
const std::string& file,
const Knobs& k) {
1733 apply_ba_knobs(k, opt);
1740 print_avg_table<T>(sn, r);
1759int solve_model_ba_bounds(
const std::string& file,
const Knobs& k) {
1762 apply_ba_knobs(k, opt);
1768 if (g_json_output) {
1770 p[
"type"] =
"BoundsTable";
1772 p[
"family"] = am.substr(0, am.find(
'.'));
1775 for (
const char* key : {
"Station",
"JobClass",
"QLower",
"QUpper",
"TLower",
"TUpper"})
1776 p[key] = line::reg::Json::array();
1777 for (std::size_t i = 0; i < sn.
nstations; ++i)
1778 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
1779 if (!b.
keep[i][c])
continue;
1780 p[
"Station"].push_back(sn.
stations[i].name);
1781 p[
"JobClass"].push_back(sn.
classes[c].name);
1782 p[
"QLower"].push_back(d(b.
Qlower(i, c)));
1783 p[
"QUpper"].push_back(d(b.
Qupper(i, c)));
1784 p[
"TLower"].push_back(d(b.
Tlower(i, c)));
1785 p[
"TUpper"].push_back(d(b.
Tupper(i, c)));
1787 emit_analysis<T>(
"bounds", p, am);
1790 std::printf(
"SolverBA arith=%s method=%s type=%s family=%s sides=%s\n",
1794 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLower",
"QUpper",
1795 "TLower",
"TUpper");
1796 for (std::size_t i = 0; i < sn.
nstations; ++i)
1797 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
1798 if (!b.
keep[i][c])
continue;
1799 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
1814int solve_model_qns(
const std::string& file,
const Knobs& k) {
1817 if (!k.method.empty()) opt.
method = k.method;
1818 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1823 opt.
timeout = k.timeout_seconds;
1832 print_avg_table<T>(sn, r);
1849 const std::vector<std::vector<line::solvers::DefaultCdfCurve>> RD =
1851 if (g_json_output) {
1853 p[
"type"] =
"CdfRespT";
1856 for (std::size_t i = 0; i < RD.size(); ++i)
1857 for (std::size_t c = 0; c < RD[i].size(); ++c) {
1858 if (RD[i][c].t.empty())
continue;
1860 e[
"Station"] = sn.
stations[i].name;
1861 e[
"JobClass"] = sn.
classes[c].name;
1869 emit_analysis<T>(
"cdf", p, std::string());
1872 std::printf(
"%s arith=%s method=%s (exponential fallback with the solver's mean)\n",
1874 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
1875 for (std::size_t i = 0; i < RD.size(); ++i)
1876 for (std::size_t c = 0; c < RD[i].size(); ++c)
1877 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
1878 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
1879 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].F[j]);
1885int solve_model_mva_cdf(
const std::string& file,
const Knobs& k) {
1888 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1889 if (k.tol >= 0.0) opt.
tol = k.tol;
1890 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
1891 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1892 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1893 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
1896 return emit_default_cdf<T>(
"SolverMVA", net.
get_struct(), r);
1901int solve_model_ag_cdf(
const std::string& file,
const Knobs& k) {
1904 apply_ag_knobs(k, opt);
1906 return emit_default_cdf<T>(
"SolverAG", net.
get_struct(), r);
1911int solve_model_ba_cdf(
const std::string& file,
const Knobs& k) {
1914 apply_ba_knobs(k, opt);
1916 return emit_default_cdf<T>(
"SolverBA", net.
get_struct(), r);
1920int solve_model_qns_cdf(
const std::string& file,
const Knobs& k) {
1923 if (!k.method.empty()) opt.
method = k.method;
1924 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1925 opt.
timeout = k.timeout_seconds;
1928 return emit_default_cdf<double>(
"SolverQNS", net.
get_struct(), r);
1941 const std::string& stage, std::size_t npriors) {
1946 p[
"type"] =
"IntervalTable";
1948 p[
"exact"] = iv.
exact;
1949 p[
"intervalMethod"] = iv.
method;
1950 if (!iv.
why.empty()) p[
"why"] = iv.
why;
1957 for (
const char* key : {
"Station",
"JobClass",
"QLen_lo",
"QLen_up",
"Util_lo",
"Util_up",
1958 "RespT_lo",
"RespT_up",
"Tput_lo",
"Tput_up"})
1959 p[key] = line::reg::Json::array();
1960 if (!g_json_output) {
1961 std::printf(
"SolverUQ arith=%s interval=%s exact=%s priors=%zu stage=%s\n",
1963 npriors, stage.c_str());
1969 std::fprintf(stderr,
1970 "Warning: exact interval MVA does not apply (%s); the range below is "
1971 "over the solved design points and is not an enclosure.\n",
1973 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s %12s %12s\n",
"Station",
1974 "JobClass",
"QLen_lo",
"QLen_up",
"Util_lo",
"Util_up",
"RespT_lo",
1975 "RespT_up",
"Tput_lo",
"Tput_up");
1977 for (std::size_t i = 0; i < sn.
nstations; ++i)
1978 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
1981 if (v(iv.
Qup, i, c) <= 0.0 && v(iv.
Uup, i, c) <= 0.0 && v(iv.
Tup, i, c) <= 0.0)
1983 if (!g_json_output) {
1985 "%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
1987 v(iv.
Qup, i, c), v(iv.
Ulo, i, c), v(iv.
Uup, i, c), v(iv.
Rlo, i, c),
1988 v(iv.
Rup, i, c), v(iv.
Tlo, i, c), v(iv.
Tup, i, c));
1991 p[
"Station"].push_back(sn.
stations[i].name);
1992 p[
"JobClass"].push_back(sn.
classes[c].name);
1993 p[
"QLen_lo"].push_back(v(iv.
Qlo, i, c));
1994 p[
"QLen_up"].push_back(v(iv.
Qup, i, c));
1995 p[
"Util_lo"].push_back(v(iv.
Ulo, i, c));
1996 p[
"Util_up"].push_back(v(iv.
Uup, i, c));
1997 p[
"RespT_lo"].push_back(v(iv.
Rlo, i, c));
1998 p[
"RespT_up"].push_back(v(iv.
Rup, i, c));
1999 p[
"Tput_lo"].push_back(v(iv.
Tlo, i, c));
2000 p[
"Tput_up"].push_back(v(iv.
Tup, i, c));
2002 if (g_json_output) emit_analysis<T>(
"interval", p, iv.
method);
2026int solve_model_uq(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
2029 if (!k.method.empty()) opt.
method = k.method;
2030 if (k.samples) opt.
samples = k.samples;
2031 if (k.seed) opt.
seed = k.seed;
2039 if (analysis ==
"interval") {
2054 if (analysis ==
"avg") {
2056 std::printf(
"SolverUQ arith=%s design=%s points=%zu priors=%zu stage=%s method=%s\n",
2059 print_avg_table<T>(sn, r.
avg);
2065 p[
"type"] =
"PosteriorTable";
2070 for (std::size_t l = 0; l < r.
sites.size(); ++l) {
2072 s[
"node"] = sn.
nodes[r.
sites[l].node - 1].name;
2074 s[
"kind"] = r.
sites[l].arrival ?
"arrival" :
"service";
2077 p[
"priors"] = sites;
2078 for (
const char* key : {
"Point",
"Weight",
"Station",
"JobClass",
"QLen",
"Util",
"RespT",
2080 p[key] = line::reg::Json::array();
2083 if (!g_json_output) {
2084 std::printf(
"SolverUQ arith=%s design=%s points=%zu priors=%zu stage=%s\n",
2089 std::printf(
"%-6s %12s substituted means\n",
"Point",
"Weight");
2090 for (std::size_t e = 0; e < r.
points.size(); ++e) {
2092 for (std::size_t l = 0; l < r.
sites.size(); ++l)
2093 std::printf(
" %s@%s=%.6g", sn.
nodes[r.
sites[l].node - 1].name.c_str(),
2098 std::printf(
"%-6s %12s %-16s %-14s %12s %12s %12s %12s\n",
"Point",
"Weight",
"Station",
2099 "JobClass",
"QLen",
"Util",
"RespT",
"Tput");
2101 for (std::size_t e = 0; e < r.
points.size(); ++e) {
2103 for (std::size_t l = 0; l < r.
sites.size(); ++l)
2105 means.push_back(row);
2107 for (std::size_t i = 0; i < sn.
nstations; ++i)
2108 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2116 if (q <= 0.0 && u <= 0.0 && t <= 0.0)
continue;
2117 if (!g_json_output) {
2118 std::printf(
"%-6zu %12.6g %-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", e + 1,
2124 p[
"Point"].push_back(e);
2126 p[
"Station"].push_back(sn.
stations[i].name);
2127 p[
"JobClass"].push_back(sn.
classes[c].name);
2128 p[
"QLen"].push_back(q);
2129 p[
"Util"].push_back(u);
2130 p[
"RespT"].push_back(rr);
2131 p[
"Tput"].push_back(t);
2134 if (g_json_output) {
2135 p[
"substitutedMean"] = means;
2136 emit_analysis<T>(
"posterior", p, r.
avg.actualmethod);
2150 std::size_t cut = 0;
2151 for (std::size_t i = 0; i < d.
cutoff.size(); ++i) cut = std::max(cut, d.
cutoff[i]);
2153 std::printf(
"SolverCTMC arith=%s method=%s type=%s states=%zu cutoff=%zu\n",
2158 std::printf(
"SolverCTMC arith=%s method=%s type=%s states=%zu\n",
2164 if (!d.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", d.
warning.c_str());
2179 m[
"states"] = d.
chain.space.size();
2180 std::size_t cut = 0;
2181 for (std::size_t i = 0; i < d.
cutoff.size(); ++i) cut = std::max(cut, d.
cutoff[i]);
2182 if (cut) m[
"cutoff"] = cut;
2197 print_ctmc_banner<T>(a.
sol);
2198 print_avg_table<T>(sn, r);
2203 std::printf(
"%-16s %-14s %12s\n",
"Region",
"JobClass",
"Parked");
2204 for (std::size_t c = 0; c < sn.
nclasses && c < a.
parked.size(); ++c)
2205 std::printf(
"%-16s %-14s %12.6g\n",
"(all)", sn.
classes[c].name.c_str(),
2232 std::size_t held = 0;
2235 "SolverCTMC arith=%s method=%s type=%s states=%lld held=%zu levels=%zu iters=%d "
2236 "encoding=%s exact=%s\n",
2244 print_avg_table<T>(sn, r);
2263 "the cftp methods draw random states and form the station balance functions in the "
2264 "log domain, neither of which exists in exact rational arithmetic; rerun with --arith "
2265 "double or --arith real");
2273 double horizon = 0.0;
2274 for (std::size_t i = 0; i < s.
horizon.size(); ++i)
2275 horizon +=
static_cast<double>(s.
horizon[i]);
2276 if (!s.
horizon.empty()) horizon /=
static_cast<double>(s.
horizon.size());
2278 "SolverCTMC arith=%s method=%s type=%s samples=%zu seed=%lu distinct=%zu "
2279 "meanhorizon=%.6g\n",
2283 print_avg_table<T>(sn, r);
2311 if (!line::ctmc::analyzer_detail::default_init_state(sn, init))
2313 "-a prob reports the probability of the model's DEFAULT INITIAL STATE, and this "
2314 "model's initial marking admits no state; check the class populations against their "
2315 "reference stations");
2323 if (!k.state.empty()) {
2326 "--state is the state of ONE node and needs --node to say which; a bare state "
2327 "vector cannot be matched against a network whose nodes have different widths");
2331 " is not a stateful node, so it holds no state to ask about");
2340 const std::size_t w = d.
chain.space.empty() ? k.state.size()
2341 : d.
chain.space[0].local[isf - 1].size();
2342 if (k.state.size() != w)
2344 "--state has " + std::to_string(k.state.size()) +
" entries but node " +
2345 std::to_string(k.node) +
" encodes its state in " + std::to_string(w) +
2346 "; getProb(node, state) takes the node's whole encoded row, and a shorter one "
2347 "padded with zeros is a different state rather than a partial one (use -a marg "
2348 "for a per-class job-count marginal)");
2350 for (std::size_t i = 0; i < k.state.size(); ++i)
2352 init.
local[isf - 1] = row;
2359 if (g_json_output) {
2361 p[
"type"] =
"ProbAggr";
2365 line::reg::Json st = line::reg::Json::array(), pm = line::reg::Json::array(),
2366 pa = line::reg::Json::array();
2367 for (std::size_t i = 0; i < sn.
nstations; ++i) {
2375 emit_analysis<T>(
"prob", p, d.
actualmethod, ctmc_meta<T>(d));
2378 print_ctmc_banner<T>(d);
2381 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
2382 for (std::size_t i = 0; i < sn.
nstations; ++i)
2383 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
2407 for (std::size_t i = 0; i < filt.size(); ++i)
2408 for (std::size_t r = 0; r < filt[i].size(); ++r) {
2409 line::reg::Json bfrom = line::reg::Json::array(), bto = line::reg::Json::array(),
2410 brate = line::reg::Json::array();
2411 for (std::size_t a = 0; a < n; ++a)
2412 for (std::size_t b = 0; b < n; ++b) {
2414 if (q == 0.0)
continue;
2419 if (bfrom.empty())
continue;
2426 blocks.push_back(e);
2437 const std::size_t n = g.
Q.rows();
2438 std::size_t nnz = 0;
2439 for (std::size_t i = 0; i < n; ++i)
2440 for (std::size_t j = 0; j < n; ++j)
2442 if (g_json_output) {
2444 p[
"type"] =
"InfGen";
2451 line::reg::Json from = line::reg::Json::array(), to = line::reg::Json::array(),
2452 rate = line::reg::Json::array();
2453 for (std::size_t i = 0; i < n; ++i)
2454 for (std::size_t j = 0; j < n; ++j) {
2456 if (q == 0.0)
continue;
2470 for (std::size_t a = 0; a < g.
sync.size() && a < g.
filt.size(); ++a) {
2475 std::size_t fnz = 0;
2476 line::reg::Json ffrom = line::reg::Json::array(), fto = line::reg::Json::array(),
2477 frate = line::reg::Json::array();
2478 for (std::size_t i = 0; i < n; ++i)
2479 for (std::size_t j = 0; j < n; ++j) {
2481 if (q == 0.0)
continue;
2492 e[
"activeNode"] = g.
sync[a].active.node;
2493 e[
"activeClass"] = g.
sync[a].active.cls;
2495 e[
"passiveNode"] = g.
sync[a].passive.node;
2496 e[
"passiveClass"] = g.
sync[a].passive.cls;
2505 p[
"startFilt"] = aux_filt_json<T>(g.
start_filt, n);
2506 p[
"preemptFilt"] = aux_filt_json<T>(g.
preempt_filt, n);
2507 emit_analysis<T>(
"gen", p, d.
actualmethod, ctmc_meta<T>(d));
2510 print_ctmc_banner<T>(d);
2511 std::printf(
"InfGen events=%zu nnz=%zu\n", g.
sync.size(), nnz);
2512 std::printf(
"%8s %8s %16s\n",
"From",
"To",
"Rate");
2513 for (std::size_t i = 0; i < n; ++i)
2514 for (std::size_t j = 0; j < n; ++j) {
2516 if (q != 0.0) std::printf(
"%8zu %8zu %16.10g\n", i + 1, j + 1, q);
2518 std::printf(
"%6s %-10s %6s %6s %-10s %6s %6s %8s\n",
"Event",
"ActEvent",
"ActNode",
"ActCls",
2519 "PasEvent",
"PasNode",
"PasCls",
"Nnz");
2520 for (std::size_t a = 0; a < g.
sync.size() && a < g.
filt.size(); ++a) {
2521 std::size_t fnz = 0;
2522 for (std::size_t i = 0; i < n; ++i)
2523 for (std::size_t j = 0; j < n; ++j)
2525 std::printf(
"%6zu %-10s %6zu %6zu %-10s %6zu %6zu %8zu\n", a + 1,
2528 g.
sync[a].passive.node, g.
sync[a].passive.cls, fnz);
2539 if (g_json_output) {
2541 p[
"type"] =
"StateSpace";
2546 p[
"space"] = matrix_json(s.
flat);
2547 p[
"spaceAggr"] = matrix_json(A);
2555 for (std::size_t f = 0; f < s.
local.size(); ++f) loc.push_back(matrix_json(s.
local[f]));
2556 p[
"localSpace"] = loc;
2560 p[
"pi"] = vector_json(d.
pi);
2561 emit_analysis<T>(
"states", p, d.
actualmethod, ctmc_meta<T>(d));
2564 print_ctmc_banner<T>(d);
2567 std::printf(
"NodeWidths");
2570 std::printf(
"%8s %12s %s\n",
"State",
"Prob",
"Detailed | Aggregate");
2571 for (std::size_t i = 0; i < s.
flat.rows(); ++i) {
2573 for (std::size_t c = 0; c < s.
flat.cols(); ++c)
2576 for (std::size_t c = 0; c < A.
cols(); ++c)
2603 std::vector<bool> queueing(M,
false);
2604 for (std::size_t i = 0; i < M; ++i)
2610 for (std::size_t s = 0; s < reward.size(); ++s)
2611 for (std::size_t i = 0; i < M; ++i)
2613 for (std::size_t c = 0; c < K; ++c) reward[s] += A(s, i * K + c);
2615 std::vector<line::ctmc::CtmcSensParam<T> > params;
2616 std::vector<std::string> skipped;
2617 for (std::size_t i = 0; i < M; ++i) {
2619 for (std::size_t c = 0; c < K; ++c) {
2622 if (!(mu > 0.0))
continue;
2623 const std::string nm =
2626 skipped.push_back(nm);
2636 params.push_back(p);
2641 "-a sens found no exponential service rate to differentiate: every enabled "
2642 "(station, class) pair carries a non-exponential distribution, and replacing one with "
2643 "an exponential of the perturbed rate would change the model rather than a parameter "
2646 const std::vector<line::ctmc::CtmcSensRank<T> > rank =
2648 if (g_json_output) {
2650 p[
"type"] =
"SensRanking";
2655 p[
"reward"] =
"mean number of jobs at the queueing stations";
2656 line::reg::Json par = line::reg::Json::array(), val = line::reg::Json::array(),
2657 S = line::reg::Json::array(), SS = line::reg::Json::array();
2658 for (std::size_t l = 0; l < rank.size(); ++l) {
2659 par.push_back(rank[l].parameter);
2660 val.push_back(rank[l].value);
2664 if (rank[l].scaled_valid)
2669 p[
"Parameter"] = par;
2672 p[
"ScaledSens"] = SS;
2674 for (std::size_t l = 0; l < skipped.size(); ++l) sk.push_back(skipped[l]);
2676 emit_analysis<T>(
"sens", p, d.
actualmethod, ctmc_meta<T>(d));
2679 print_ctmc_banner<T>(d);
2680 std::printf(
"Reward mean number of jobs at the queueing stations\n");
2681 std::printf(
"%-28s %14s %16s %16s\n",
"Parameter",
"Value",
"Sens",
"ScaledSens");
2682 for (std::size_t l = 0; l < rank.size(); ++l) {
2683 if (rank[l].scaled_valid)
2684 std::printf(
"%-28s %14.6g %16.8g %16.8g\n", rank[l].parameter.c_str(), rank[l].value,
2690 std::printf(
"%-28s %14.6g %16.8g %16s\n", rank[l].parameter.c_str(), rank[l].value,
2693 for (std::size_t l = 0; l < skipped.size(); ++l)
2694 std::printf(
"Skipped %s: service is not exponential\n", skipped[l].c_str());
2701 std::vector<std::string> names;
2703 if (g_json_output) {
2705 p[
"type"] =
"AvgReward";
2707 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
2709 p[
"E"] = vector_json(r);
2712 emit_analysis<T>(
"reward", p, std::string());
2716 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
2717 for (std::size_t l = 0; l < r.size(); ++l)
2739 if (k.reward_name.empty())
2741 "-a reward-value returns the value function of ONE reward and needs --reward-name to "
2742 "say which; -a reward returns the steady-state expectation of every declared reward");
2744 std::size_t which = rr.
names.size();
2745 for (std::size_t l = 0; l < rr.
names.size(); ++l)
2746 if (rr.
names[l] == k.reward_name) which = l;
2747 if (which == rr.
names.size()) {
2749 for (std::size_t l = 0; l < rr.
names.size(); ++l)
2750 avail += (l ?
", " :
"") + rr.
names[l];
2752 "' is not declared by this model; it declares: " +
2753 (avail.empty() ? std::string(
"(none)") : avail));
2757 if (g_json_output) {
2759 p[
"type"] =
"RewardValueFunction";
2761 p[
"Reward"] = rr.
names[which];
2762 p[
"steps"] = V.
rows();
2763 p[
"states"] = V.
cols();
2764 p[
"t"] = vector_json(rr.
t);
2765 p[
"V"] = matrix_json(V);
2767 emit_analysis<T>(
"rewardvalue", p, std::string());
2770 std::printf(
"SolverCTMC arith=%s reward=%s steps=%zu states=%zu\n",
2772 std::printf(
"%-10s %-10s %20s\n",
"Step",
"State",
"V");
2773 for (std::size_t i = 0; i < V.
rows(); ++i)
2774 for (std::size_t j = 0; j < V.
cols(); ++j)
2797 (void)sn; (void)opt; (void)k;
2799 "-a tranreward integrates the forward equation, which needs transcendental "
2800 "arithmetic; rerun with --arith double or --arith real");
2804 "-a tranreward integrates E[r(X(t))] and needs a horizon: pass --tspan <t0>:<t1>");
2805 std::vector<std::string> names;
2810 if (g_json_output) {
2812 p[
"type"] =
"TranReward";
2815 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
2817 p[
"t"] = vector_json<T>(t);
2819 for (std::size_t l = 0; l < r.size(); ++l) e.push_back(vector_json<T>(r[l]));
2821 emit_analysis<T>(
"tranreward", p, std::string());
2824 std::printf(
"SolverCTMC arith=%s rewards=%zu points=%zu tspan=[%g,%g]\n",
2826 std::printf(
"%-16s",
"t");
2827 for (std::size_t l = 0; l < names.size(); ++l) std::printf(
" %16s", names[l].c_str());
2829 for (std::size_t i = 0; i < t.size(); ++i) {
2831 for (std::size_t l = 0; l < r.size(); ++l)
2853 "-a tranprob integrates pi(t) and needs a horizon: pass --tspan <t0>:<t1>");
2862 if (g_json_output) {
2864 p[
"type"] =
"TranProb";
2866 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
2867 if (k.node) p[
"node"] = k.node - 1;
2869 span.push_back(k.t0);
2870 span.push_back(k.t1);
2875 p[
"t"] = vector_json(det.
t);
2880 p[
"labels"] = matrix_json(det.
labels);
2881 p[
"labelsAggr"] = matrix_json(agg.
labels);
2882 p[
"pit"] = matrix_json(det.
pit);
2883 p[
"pitAggr"] = matrix_json(agg.
pit);
2884 emit_analysis<T>(
"tranprob", p, tr.chain.actualmethod, ctmc_meta<T>(tr.chain));
2887 print_ctmc_banner<T>(tr.chain);
2888 std::printf(
"TranProb times=%zu tspan=%g:%g scope=%s\n", det.
t.size(), k.t0, k.t1,
2889 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
2892 std::printf(
"%8s %s\n",
"State",
"Detailed | Aggregate");
2893 for (std::size_t s = 0; s < det.
labels.rows(); ++s) {
2894 std::printf(
"%8zu ", s + 1);
2895 for (std::size_t c = 0; c < det.
labels.cols(); ++c)
2898 for (std::size_t c = 0; c < agg.
labels.cols(); ++c)
2902 std::printf(
"%14s",
"Time");
2903 for (std::size_t s = 0; s < det.
pit.cols(); ++s) std::printf(
" %12zu", s + 1);
2905 for (std::size_t i = 0; i < det.
t.size(); ++i) {
2907 for (std::size_t s = 0; s < det.
pit.cols(); ++s)
2936 "-a tran integrates the forward equation and needs a horizon: pass --tspan <t0>:<t1>");
2941 if (g_json_output) {
2943 p[
"type"] =
"TranAvgTable";
2950 for (std::size_t i = 0; i < M; ++i)
2951 for (std::size_t c = 0; c < K; ++c) {
2958 e[
"Station"] = sn.
stations[i].name;
2959 e[
"JobClass"] = sn.
classes[c].name;
2963 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
2964 x = line::reg::Json::array();
2965 for (std::size_t j = 0; j < nt; ++j) {
2976 emit_analysis<T>(
"tran", p, tr.chain.actualmethod, ctmc_meta<T>(tr.chain));
2979 print_ctmc_banner<T>(tr.chain);
2980 std::printf(
"TranAvg times=%zu tspan=%g:%g\n", nt, k.t0, k.t1);
2981 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
2983 for (std::size_t i = 0; i < M; ++i)
2984 for (std::size_t c = 0; c < K; ++c) {
2986 for (std::size_t j = 0; j < nt; ++j)
2987 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
3011 const std::size_t nevents = k.events ? k.events : (k.samples ? k.samples : 1000);
3012 const unsigned long seed = k.seed ? k.seed : 23000;
3021 if (g_json_output) {
3023 p[
"type"] =
"SamplePath";
3027 p[
"events"] = nevents;
3029 p[
"drawn"] = path.
state.size();
3030 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
3031 if (k.node) p[
"node"] = k.node - 1;
3032 p[
"t"] = vector_json(path.
t);
3033 p[
"state"] = index_json(path.
state);
3035 for (std::size_t i = 0; i < path.
state.size(); ++i) {
3039 if (i < path.
event.size() && path.
event[i] !=
static_cast<std::size_t
>(-1))
3040 ev.push_back(path.
event[i]);
3045 p[
"sysAggr"] = matrix_json(A);
3053 p[
"space"] = matrix_json(s.
flat);
3057 p[
"nodeState"] = matrix_json(L);
3058 p[
"nodeAggr"] = matrix_json(LA);
3060 emit_analysis<T>(
"sample", p, path.
chain.actualmethod, ctmc_meta<T>(path.
chain));
3065 std::printf(
"SolverCTMC arith=%s states=%zu events=%zu seed=%lu drawn=%zu scope=%s\n",
3067 path.
state.size(), k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
3068 std::printf(
"%14s %8s %8s %s\n",
"Time",
"State",
"Event",
3069 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
3070 for (std::size_t i = 0; i < path.
state.size(); ++i) {
3071 const std::size_t ev = i < path.
event.size() ? path.
event[i] :
static_cast<std::size_t
>(-1);
3073 if (ev ==
static_cast<std::size_t
>(-1))
3074 std::printf(
"%8s ",
"absorb");
3076 std::printf(
"%8zu ", ev + 1);
3077 for (std::size_t c = 0; c < A.
cols(); ++c)
3081 for (std::size_t c = 0; c < L.
cols(); ++c)
3084 for (std::size_t c = 0; c < LA.
cols(); ++c)
3095 const std::vector<std::vector<line::ctmc::CdfCurve<T> > > RD =
3098 if (g_json_output) {
3100 p[
"type"] =
"CdfRespT";
3107 for (std::size_t i = 0; i < RD.size(); ++i)
3108 for (std::size_t c = 0; c < RD[i].size(); ++c) {
3112 if (RD[i][c].empty())
continue;
3114 e[
"Station"] = sn.
stations[i].name;
3115 e[
"JobClass"] = sn.
classes[c].name;
3118 e[
"t"] = vector_json(RD[i][c].t);
3119 e[
"F"] = vector_json(RD[i][c].F);
3124 for (std::size_t c = 0; c < RS.size(); ++c) {
3125 if (RS[c].empty())
continue;
3128 e[
"t"] = vector_json(RS[c].t);
3129 e[
"F"] = vector_json(RS[c].F);
3137 emit_analysis<T>(
"cdf", p, std::string());
3141 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
3142 for (std::size_t i = 0; i < RD.size(); ++i)
3143 for (std::size_t c = 0; c < RD[i].size(); ++c) {
3146 if (RD[i][c].empty())
continue;
3147 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
3148 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
3153 std::printf(
"%-16s %-14s %14s %14s\n",
"System",
"Chain",
"Time",
"F(t)");
3154 for (std::size_t c = 0; c < RS.size(); ++c) {
3155 if (RS[c].empty())
continue;
3156 for (std::size_t j = 0; j < RS[c].t.size(); ++j)
3157 std::printf(
"%-16s %-14zu %14.8g %14.10g\n",
"(system)", c + 1,
3172 std::vector<std::vector<double>> rows;
3173 std::size_t pos = 0;
3174 while (pos <= spec.size()) {
3175 std::size_t semi = spec.find(
';', pos);
3176 if (semi == std::string::npos) semi = spec.size();
3177 std::string rowtxt = spec.substr(pos, semi - pos);
3178 std::vector<double> row;
3180 while (p2 <= rowtxt.size()) {
3181 std::size_t comma = rowtxt.find(
',', p2);
3182 if (comma == std::string::npos) comma = rowtxt.size();
3183 std::string cell = rowtxt.substr(p2, comma - p2);
3184 if (!cell.empty()) {
3186 const double v = std::strtod(cell.c_str(), &endp);
3187 if (endp == cell.c_str() || *endp !=
'\0')
3189 "' is not a number");
3194 if (!row.empty()) rows.push_back(row);
3198 for (std::size_t i = 1; i < rows.size(); ++i)
3199 if (rows[i].size() != rows[0].size())
3201 ": every state row must have the same width");
3203 for (std::size_t i = 0; i < rows.size(); ++i)
3204 for (std::size_t j = 0; j < rows[i].size(); ++j) out(i, j) = rows[i][j];
3217 if (k.passage_into.empty())
3219 "-a firstpasst times the passage INTO a state set and needs --passage-into; name it "
3220 "as 1-based rows of the state space ('3,5') or as state rows ('0,2;1,1')");
3223 const std::string method = k.passage_method.empty() ?
"expm" : k.passage_method;
3228 if (g_json_output) {
3230 p[
"type"] =
"CdfFirstPassT";
3235 line::reg::Json src = line::reg::Json::array(), tgt = line::reg::Json::array();
3236 for (std::size_t i : fp.
source) src.push_back(
static_cast<double>(i));
3237 for (std::size_t i : fp.
target) tgt.push_back(
static_cast<double>(i));
3240 emit_analysis<T>(
"firstpasst", p, method);
3243 std::printf(
"SolverCTMC arith=%s method=%s getCdfFirstPassT\n",
3245 std::printf(
"source states: %zu%s, target states: %zu\n", fp.
source.size(),
3246 fp.
source.empty() ?
" (conditional stationary law)" :
"", fp.
target.size());
3247 std::printf(
"%14s %14s %14s\n",
"Time",
"F(t)",
"f(t)");
3248 for (std::size_t j = 0; j < fp.
t.size(); j += 111)
3249 std::printf(
"%14.8g %14.10g %14.10g\n", fp.
t[j], fp.
F[j], fp.
f[j]);
3250 std::printf(
"%14.8g %14.10g %14.10g\n", fp.
t.back(), fp.
F.back(), fp.
f.back());
3266 if (k.passage_into.empty())
3268 "-a firstpasstmom times the passage INTO a state set and needs --passage-into; name "
3269 "it as 1-based rows of the state space ('3,5') or as state rows ('0,2;1,1')");
3272 const std::size_t nmax = (k.passage_orders > 0) ? k.passage_orders : 3;
3277 if (g_json_output) {
3279 p[
"type"] =
"FirstPassTMoments";
3282 for (std::size_t i = 0; i < fm.
m.size(); ++i)
3286 for (std::size_t i = 0; i < fm.
mall.rows(); ++i) {
3288 for (std::size_t j = 0; j < fm.
mall.cols(); ++j)
3290 mall.push_back(row);
3293 line::reg::Json src = line::reg::Json::array(), tgt = line::reg::Json::array();
3294 for (std::size_t i : fm.
source) src.push_back(
static_cast<double>(i));
3295 for (std::size_t i : fm.
target) tgt.push_back(
static_cast<double>(i));
3298 emit_analysis<T>(
"firstpasstmom", p,
"moments");
3302 std::printf(
"source states: %zu%s, target states: %zu\n", fm.
source.size(),
3303 fm.
source.empty() ?
" (conditional stationary law)" :
"", fm.
target.size());
3304 std::printf(
"%8s %20s\n",
"Order",
"Moment");
3305 for (std::size_t i = 0; i < fm.
m.size(); ++i)
3327int solve_model_ctmc(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
3330 if (!k.method.empty()) opt.
method = k.method;
3331 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
3333 opt.
force = k.force;
3334 if (k.timestep > 0.0) opt.
timestep = k.timestep;
3337 if (!k.transient_method.empty()) opt.
transient_method = k.transient_method;
3338 if (k.fau_epsilon > 0.0) opt.
fau_epsilon = k.fau_epsilon;
3339 if (k.fau_delta >= 0.0) opt.
fau_delta = k.fau_delta;
3346 if (opt.
method ==
"mdd") {
3348 if (k.mdd_tol > 0.0) mcdopt.
tol = k.mdd_tol;
3349 if (k.mdd_maxiter > 0) mcdopt.
maxiter = k.mdd_maxiter;
3350 return solve_ctmc_mdd_avg<T>(sn, opt, mcdopt);
3352 if (opt.
method ==
"cftp" || opt.
method ==
"cftp.approx") {
3354 if (k.samples) cftpopt.
samples = k.samples;
3355 if (k.seed) cftpopt.
seed = k.seed;
3356 return solve_ctmc_cftp_avg<T>(sn, opt, cftpopt);
3361 if (analysis ==
"avg")
return solve_ctmc_avg<T>(sn, opt);
3362 if (analysis ==
"prob")
return solve_ctmc_prob<T>(sn, opt, k);
3363 if (analysis ==
"gen")
return solve_ctmc_gen<T>(sn, opt);
3364 if (analysis ==
"states")
return solve_ctmc_states<T>(sn, opt);
3365 if (analysis ==
"sens")
return solve_ctmc_sens<T>(sn, opt);
3366 if (analysis ==
"reward")
return solve_ctmc_reward<T>(sn, opt);
3367 if (analysis ==
"rewardvalue")
return solve_ctmc_reward_value<T>(sn, opt, k);
3375 "the -a " + analysis +
3376 " analysis integrates the forward equation, draws exponential clocks or takes a matrix "
3377 "exponential, none of which exists in exact rational arithmetic; rerun with --arith "
3378 "double or --arith real");
3380 if (analysis ==
"tran")
return solve_ctmc_tran<T>(sn, opt, k);
3381 if (analysis ==
"tranprob")
return solve_ctmc_tranprob<T>(sn, opt, k);
3382 if (analysis ==
"tranreward")
return solve_ctmc_tran_reward<T>(sn, opt, k);
3383 if (analysis ==
"sample")
return solve_ctmc_sample<T>(sn, opt, k);
3384 if (analysis ==
"firstpasst")
return solve_ctmc_firstpasst<T>(sn, opt, k);
3385 if (analysis ==
"firstpasstmom")
return solve_ctmc_firstpasst_moments<T>(sn, opt, k);
3386 return solve_ctmc_cdf<T>(sn, opt);
3406int solve_model_ssa_prob(
const std::string& file,
const Knobs& k) {
3415 if (k.samples) opt.
samples = k.samples;
3416 if (k.seed) opt.
seed = k.seed;
3417 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
3418 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
3421 if (g_json_output) {
3423 p[
"type"] =
"ProbAggr";
3429 p[
"ProbSysSeen"] = r.
sys.
seen;
3431 line::reg::Json st = line::reg::Json::array(), pm = line::reg::Json::array(),
3432 pa = line::reg::Json::array(), sm = line::reg::Json::array();
3433 for (std::size_t i = 0; i < sn.
nstations; ++i) {
3435 pm.push_back(r.
marg[i].prob);
3436 pa.push_back(r.
aggr[i].prob);
3437 sm.push_back(r.
marg[i].seen);
3443 emit_analysis<T>(
"prob", p,
"serial");
3446 std::printf(
"SolverSSA arith=%s method=serial samples=%zu seed=%lu time=%.6g\n",
3448 std::printf(
"ProbSys = %.8g%s\n", r.
sys.
prob, r.
sys.
seen ?
"" :
" (state never visited)");
3451 std::printf(
"%-20s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
3452 for (std::size_t i = 0; i < sn.
nstations; ++i)
3453 std::printf(
"%-20s %14.8g %14.8g\n", sn.
stations[i].name.c_str(), r.
marg[i].prob,
3471int solve_model_ssa_sample(
const std::string& file,
const Knobs& k) {
3476 opt.
samples = k.events ? k.events : (k.samples ? k.samples : 1000);
3477 if (k.seed) opt.
seed = k.seed;
3478 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
3479 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
3485 if (g_json_output) {
3487 p[
"type"] =
"SamplePath";
3490 p[
"seed"] = sys.
seed;
3491 p[
"drawn"] = sys.
t.size();
3492 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
3493 if (k.node) p[
"node"] = k.node - 1;
3494 p[
"t"] = vector_json(sys.
t);
3495 p[
"event"] = index_json(sys.
event);
3496 p[
"state"] = matrix_json(sys.
state);
3505 w.push_back(sim.
run.space.empty() ? 0 : sim.
run.space[0].local[f].size());
3506 p[
"NodeWidths"] = w;
3508 p[
"sysAggr"] = matrix_json(sys.
aggr);
3510 p[
"nodeState"] = matrix_json(nodep.
state);
3511 p[
"nodeAggr"] = matrix_json(nodep.
aggr);
3513 emit_analysis<T>(
"sample", p,
"serial");
3516 std::printf(
"SolverSSA arith=%s method=serial events=%zu seed=%lu drawn=%zu scope=%s\n",
3518 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
3519 std::printf(
"%14s %8s %s\n",
"Time",
"Event",
3520 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
3521 for (std::size_t i = 0; i < sys.
t.size(); ++i) {
3522 std::printf(
"%14.8g %8zu ", sys.
t[i], sys.
event[i]);
3523 for (std::size_t c = 0; c < sys.
aggr.cols(); ++c)
3527 for (std::size_t c = 0; c < nodep.
state.cols(); ++c)
3530 for (std::size_t c = 0; c < nodep.
aggr.cols(); ++c)
3550 for (std::size_t i = 0; i < m.
rows(); ++i)
3551 for (std::size_t j = 0; j < m.
cols(); ++j)
3577int solve_model_ssa(
const std::string& file,
const Knobs& k) {
3580 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
3581 if (k.samples) opt.
samples = k.samples;
3582 if (k.seed) opt.
seed = k.seed;
3583 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
3589 std::vector<line::ssa::SsaCacheRatio> cacheratio;
3601 std::printf(
"SolverSSA arith=%s method=%s type=%s samples=%zu seed=%lu time=%.6g\n",
3622 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
3632 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
3637 row.w = line::num_traits<T>::to_double(WN(i, c));
3641 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
3643 : line::num_traits<T>::to_double(AN(i, c));
3653 if (!k.method.empty()) opt.
method = k.method;
3654 if (k.tol >= 0.0) opt.
tol = k.tol;
3655 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
3656 if (k.iter_max >= 0) opt.
iter_max =
static_cast<std::size_t
>(k.iter_max);
3662 if (k.pstar > 0.0) {
3663 opt.
pstar = k.pstar;
3683int solve_model_fluid(
const std::string& file,
const Knobs& k) {
3701 &refreshed, &cache);
3722 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
3723 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
3728 row.w = line::num_traits<T>::to_double(WN(i, c));
3734 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
3736 : line::num_traits<T>::to_double(AN(i, c));
3757int solve_model_fluid_statevec(
const std::string& file,
const Knobs& k) {
3764 "-a statevec reports the converged ODE state and this solve produced none; the rmf "
3765 "and closing branches integrate a drift and fill it, so a branch that returns means "
3766 "directly has no state vector to report");
3770 std::vector<std::size_t> ist, cls, phs;
3771 for (std::size_t i = 0; i < sn.
nstations; ++i)
3772 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
3773 const std::size_t np = sn.
phases_of(i + 1, c + 1);
3774 for (std::size_t j = 0; j < np; ++j) {
3783 const bool labelled = ist.size() == r.
xvec.size();
3785 if (g_json_output) {
3787 p[
"type"] =
"FluidStateVec";
3789 p[
"xvec"] = vector_json(r.
xvec);
3790 p[
"labelled"] = labelled;
3792 line::reg::Json st = line::reg::Json::array(), cl = line::reg::Json::array(),
3793 ph = line::reg::Json::array();
3794 for (std::size_t j = 0; j < ist.size(); ++j) {
3795 st.push_back(sn.
stations[ist[j]].name);
3796 cl.push_back(sn.
classes[cls[j]].name);
3797 ph.push_back(phs[j]);
3803 emit_analysis<T>(
"statevec", p, r.
method);
3809 std::printf(
"# the ODE state is %zu wide and the (station, class, phase) layout accounts "
3810 "for %zu; the coordinates are printed unlabelled\n",
3811 r.
xvec.size(), ist.size());
3812 std::printf(
"%-10s %20s\n",
"Index",
"x");
3813 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
3814 std::printf(
"%-10zu %20.10g\n", j, r.
xvec[j]);
3817 std::printf(
"%-16s %-14s %-8s %20s\n",
"Station",
"JobClass",
"Phase",
"x");
3818 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
3819 std::printf(
"%-16s %-14s %-8zu %20.10g\n", sn.
stations[ist[j]].name.c_str(),
3820 sn.
classes[cls[j]].name.c_str(), phs[j], r.
xvec[j]);
3858 line::env::dispatch_detail::env_take_statevec(out);
3869int solve_model_env(
const std::string& file,
const Knobs& k) {
3872 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
3873 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
3874 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
3876 if (k.tran_points) opt.
tran_points = k.tran_points;
3877 if (k.tol >= 0.0) opt.
stage.
tol = k.tol;
3895 if (!k.stage_solver.empty()) opt.
stage_solver = k.stage_solver;
3916 std::printf(
"SolverENV arith=%s method=%s stages=%zu (closed-form limit)\n",
3918 else if (r.
method ==
"statevec")
3919 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g iters=%d%s\n",
3926 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g points=%zu iters=%d%s\n",
3929 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
3931 row.q = line::num_traits<T>::to_double(r.QN(i, c));
3932 row.u = line::num_traits<T>::to_double(r.UN(i, c));
3933 row.t = line::num_traits<T>::to_double(r.TN(i, c));
3934 row.r = std::numeric_limits<double>::quiet_NaN();
3935 row.a = std::numeric_limits<double>::quiet_NaN();
3936 row.w = row.q / row.t;
3955int solve_model_fluid_var(
const std::string& file,
const Knobs& k) {
3962 "-a var needs a fluid method that computes a second moment: 'minnormal', 'refined' or "
3963 "'dae' for the stationary covariance, 'kp' for the covariance along the trajectory. "
3965 r.
method +
"' method integrates the mean only");
3967 if (g_json_output) {
3969 p[
"type"] =
"QueueLengthVariance";
3971 p[
"Station"] = line::reg::Json::array();
3972 p[
"JobClass"] = line::reg::Json::array();
3973 p[
"QVar"] = line::reg::Json::array();
3974 p[
"QStd"] = line::reg::Json::array();
3975 for (std::size_t i = 0; i < sn.
nstations; ++i)
3976 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
3978 p[
"Station"].push_back(sn.
stations[i].name);
3979 p[
"JobClass"].push_back(sn.
classes[c].name);
3984 emit_analysis<T>(
"var", p, r.
method);
3989 std::printf(
"%-16s %-14s %12s %12s\n",
"Station",
"JobClass",
"QVar",
"QStd");
3990 for (std::size_t i = 0; i < sn.
nstations; ++i)
3991 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
3993 std::printf(
"%-16s %-14s %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4009int solve_model_fluid_odes(
const std::string& file,
const Knobs& k) {
4012 if (!k.method.empty()) opt.
method = k.method;
4013 if (k.tol >= 0.0) opt.
tol = k.tol;
4020 const std::string notation = k.notation.empty() ?
"scalar" : k.notation;
4022 if (g_json_output) {
4025 p[
"notation"] = notation;
4030 emit_analysis<T>(
"odes", p,
4031 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
4034 std::printf(
"%s\n", tex.c_str());
4050int solve_model_fluid_jacobian(
const std::string& file,
const Knobs& k) {
4053 if (!k.method.empty()) opt.
method = k.method;
4060 std::string m = opt.
method;
4061 if (m.compare(0, 6,
"fluid.") == 0) m = m.substr(6);
4065 if (!k.symbolic.empty()) symopt.
backend = k.symbolic;
4069 if (g_json_output) {
4071 p[
"type"] =
"Jacobian";
4072 p[
"engine"] = jac.
engine;
4076 for (std::size_t i = 0; i < jac.
J.size(); ++i) rows.push_back(
line::reg::Json(jac.
J[i]));
4077 p[
"jacobian"] = rows;
4082 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e) {
4084 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4086 one[it->first] = it->second;
4089 p[
"equilibria"] = eqs;
4090 emit_analysis<T>(
"jacobian", p,
4091 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
4095 std::printf(
"engine=%s states=%zu\n", jac.
engine.c_str(), jac.
vars.size());
4096 for (std::size_t i = 0; i < jac.
rhs.size(); ++i)
4097 std::printf(
"d%s/dt = %s\n", jac.
vars[i].c_str(), jac.
rhs[i].c_str());
4098 for (std::size_t i = 0; i < jac.
J.size(); ++i)
4099 for (std::size_t j = 0; j < jac.
J[i].size(); ++j) {
4102 std::printf(
"J[%s,%s] = %s\n", jac.
vars[i].c_str(), jac.
vars[j].c_str(),
4103 jac.
J[i][j].c_str());
4107 std::printf(
"equilibria: none in closed form (the solve found none, which is not a "
4108 "proof that none exist)\n");
4109 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e)
4110 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4112 std::printf(
"equilibrium %zu: %s = %s\n", e + 1, it->first.c_str(),
4113 it->second.c_str());
4130int solve_model_fluid_tranvar(
const std::string& file,
const Knobs& k) {
4138 if (g_json_output) {
4140 p[
"type"] =
"TranAvgVarTable";
4142 p[
"t0"] = tr.t.front();
4143 p[
"t1"] = tr.t.back();
4145 for (std::size_t j = 0; j < tr.t.size(); ++j) ts.push_back(tr.t[j]);
4148 for (std::size_t i = 0; i < sn.
nstations; ++i)
4149 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4152 e[
"Station"] = sn.
stations[i].name;
4153 e[
"JobClass"] = sn.
classes[c].name;
4157 for (std::size_t j = 0; j < tr.QVar.size(); ++j) v.push_back(tr.QVar[j](i, c));
4166 for (std::size_t j = 0; j < tr.Sigma.size(); ++j)
4167 sig.push_back(matrix_json<double>(tr.Sigma[j]));
4169 emit_analysis<T>(
"tranvar", p,
"kp");
4172 std::printf(
"SolverFluid arith=%s method=kp tspan=[%g,%g] points=%zu dim=%zu\n",
4174 tr.Sigma.empty() ? std::size_t(0) : tr.Sigma.front().rows());
4175 std::printf(
"%-16s %-14s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QVar",
"QStd");
4176 for (std::size_t i = 0; i < sn.
nstations; ++i)
4177 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4179 for (std::size_t j = 0; j < tr.QVar.size(); ++j) {
4180 const double var = tr.QVar[j](i, c);
4181 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4182 sn.
classes[c].name.c_str(), tr.t[j], var,
4183 var >= 0.0 ? std::sqrt(var) : std::numeric_limits<double>::quiet_NaN());
4208int solve_model_fluid_tran(
const std::string& file,
const Knobs& k) {
4212 const std::vector<line::fluid::FluidTranPoint> tr =
4216 if (g_json_output) {
4218 p[
"type"] =
"TranAvgTable";
4221 p[
"t1"] = tr.back().t;
4223 for (std::size_t j = 0; j < tr.size(); ++j) ts.push_back(tr[j].t);
4225 for (std::size_t i = 0; i < sn.
nstations; ++i)
4226 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4229 e[
"Station"] = sn.
stations[i].name;
4230 e[
"JobClass"] = sn.
classes[c].name;
4234 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
4235 x = line::reg::Json::array();
4236 for (std::size_t j = 0; j < tr.size(); ++j) {
4237 q.push_back(tr[j].
QN(i, c));
4238 u.push_back(tr[j].
UN(i, c));
4239 x.push_back(tr[j].TN(i, c));
4247 emit_analysis<T>(
"tran", p,
"closing");
4250 std::printf(
"SolverFluid arith=%s method=closing tspan=[0,%g] points=%zu\n",
4252 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
4254 for (std::size_t i = 0; i < sn.
nstations; ++i)
4255 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4257 for (std::size_t j = 0; j < tr.size(); ++j)
4258 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
4260 tr[j].QN(i, c), tr[j].UN(i, c), tr[j].TN(i, c));
4280int solve_model_fluid_prob(
const std::string& file,
const Knobs& k) {
4287 for (std::size_t i = 0; i < sn.
nstations; ++i)
4290 if (g_json_output) {
4292 p[
"type"] =
"ProbAggr";
4294 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
4295 lp = line::reg::Json::array();
4296 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4298 pa.push_back(pr[i]);
4299 lp.push_back(lg[i]);
4303 p[
"logProbAggr"] = lp;
4304 emit_analysis<T>(
"prob", p, r.
method);
4309 std::printf(
"%-16s %14s %14s\n",
"Station",
"ProbAggr",
"logProbAggr");
4310 for (std::size_t i = 0; i < sn.
nstations; ++i)
4311 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(), pr[i], lg[i]);
4326int solve_model_fluid_cdf(
const std::string& file,
const Knobs& k) {
4330 const std::vector<std::vector<line::fluid::FluidPassage> > RD =
4333 if (g_json_output) {
4335 p[
"type"] =
"CdfRespT";
4338 for (std::size_t i = 0; i < RD.size(); ++i)
4339 for (std::size_t c = 0; c < RD[i].size(); ++c) {
4343 if (RD[i][c].t.empty())
continue;
4345 e[
"Station"] = sn.
stations[i].name;
4346 e[
"JobClass"] = sn.
classes[c].name;
4354 emit_analysis<T>(
"cdf", p, std::string());
4358 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
4359 for (std::size_t i = 0; i < RD.size(); ++i)
4360 for (std::size_t c = 0; c < RD[i].size(); ++c)
4361 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
4362 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
4363 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].cdf[j]);
4382int solve_model_fluid_aoi(
const std::string& file,
const Knobs& k) {
4388 "-a aoi reports the age of a status-update system and needs the topology the age laws "
4389 "are defined for: " +
4390 (top.
error.empty() ? std::string(
"this model is not one") : top.
error));
4392 const std::string requested = line::fluid::detail::fluid_unqualify(opt.
method);
4393 if (requested !=
"default" && requested !=
"mfq")
4395 "-a aoi is the AoI branch of the 'mfq' method; '" + requested +
4396 "' integrates the mean-field drift and carries no age process");
4401 "-a aoi: the 'mfq' method did not take its AoI branch on this model");
4406 const std::size_t np = 200;
4407 std::vector<double> tv(np), fa(np), fp(np);
4408 for (std::size_t j = 0; j < np; ++j) {
4409 tv[j] = 5.0 * base *
static_cast<double>(j) /
static_cast<double>(np - 1);
4413 const double asd = std::sqrt(std::max(0.0, r.
aoi.
aoi.
var));
4414 const double psd = std::sqrt(std::max(0.0, r.
aoi.
paoi.
var));
4416 if (g_json_output) {
4437 p[
"AoI_A"] = matrix_json(r.
aoi.
aoi.
A);
4440 p[
"PAoI_A"] = matrix_json(r.
aoi.
paoi.
A);
4442 emit_analysis<T>(
"aoi", p, r.
method);
4445 std::printf(
"SolverFluid arith=%s method=mfq system=%s preemption=%.6g\n",
4447 std::printf(
"%-8s %14s %14s %14s\n",
"Metric",
"Mean",
"Var",
"Std");
4450 std::printf(
"%14s %14s %14s\n",
"Time",
"F_AoI(t)",
"F_PAoI(t)");
4451 for (std::size_t j = 0; j < np; ++j)
4452 std::printf(
"%14.8g %14.10g %14.10g\n", tv[j], fa[j], fp[j]);
4463int solve_model_prob(
const std::string& file,
const Knobs& k) {
4466 "the -a prob analysis fits a binomial/product-form law and needs transcendental "
4467 "arithmetic; rerun with --arith double or --arith real");
4471 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4472 if (k.tol >= 0.0) opt.
tol = k.tol;
4473 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4474 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4480 if (g_json_output) {
4482 p[
"type"] =
"ProbAggr";
4485 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array();
4486 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4500 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
4501 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4504 std::printf(
"%-16s %14.10g\n", sn.
stations[i].name.c_str(),
4528int solve_model_marg(
const std::string& file,
const Knobs& k) {
4531 "the -a marg analysis fits a binomial / Poisson / geometric law and needs "
4532 "transcendental arithmetic; rerun with --arith double or --arith real");
4536 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4537 if (k.tol >= 0.0) opt.
tol = k.tol;
4538 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4539 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4544 std::vector<std::size_t> ists;
4548 " exceeds the number of nodes in the model (" +
4550 const std::size_t ist = sn.
nodes[k.node - 1].station;
4553 sn.
nodes[k.node - 1].name +
4554 "') is not a station, and a queue-length distribution is "
4555 "reported per station");
4556 ists.push_back(ist);
4558 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
4560 std::vector<std::size_t> rs;
4564 " exceeds the number of classes in the model");
4565 rs.push_back(k.jobclass);
4567 for (std::size_t c = 0; c < sn.
nclasses; ++c) rs.push_back(c + 1);
4572 std::vector<line::mva::MargResult<T> > curves;
4573 for (std::size_t a = 0; a < ists.size(); ++a)
4574 for (std::size_t b = 0; b < rs.size(); ++b)
4576 k.marg_states, opt.
method));
4578 if (g_json_output) {
4580 p[
"type"] =
"ProbMarg";
4583 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
4584 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
4587 e[
"station"] = ists[a] - 1;
4588 e[
"Station"] = sn.
stations[ists[a] - 1].name;
4589 e[
"jobclass"] = rs[b] - 1;
4590 e[
"JobClass"] = sn.
classes[rs[b] - 1].name;
4592 for (std::size_t n = 0; n < m.
P.size(); ++n)
4593 jobs.push_back(k.marg_states.empty() ?
static_cast<long>(n)
4594 : k.marg_states[n]);
4596 e[
"P"] = vector_json(m.
P);
4597 e[
"logP"] = vector_json(m.
logP);
4600 p[
"marginal"] = arr;
4607 std::printf(
"%-16s %-14s %-8s %16s\n",
"Station",
"JobClass",
"Jobs",
"ProbMarg");
4608 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
4609 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
4611 for (std::size_t n = 0; n < m.
P.size(); ++n)
4612 std::printf(
"%-16s %-14s %-8ld %16.10g\n",
4613 sn.
stations[ists[a] - 1].name.c_str(),
4614 sn.
classes[rs[b] - 1].name.c_str(),
4615 k.marg_states.empty() ?
static_cast<long>(n) : k.marg_states[n],
4654int solve_model_nc_busyp(
const std::string& file,
const Knobs& k) {
4655 if (k.busy_subnet.empty())
4657 "-a busyperiod needs --busyperiod-subnet: the busy period is defined for a named "
4658 "subnetwork of stations, and no default can choose one");
4661 std::vector<std::size_t> subnet;
4662 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t) {
4665 std::to_string(k.busy_subnet[t]) +
", beyond the model's " +
4667 subnet.push_back(k.busy_subnet[t] - 1);
4669 std::vector<std::size_t> orders = k.busy_orders;
4670 if (orders.empty()) orders.push_back(1);
4673 if (g_json_output) {
4675 p[
"type"] =
"BusyPeriod";
4678 for (std::size_t t = 0; t < subnet.size(); ++t) sj.push_back(subnet[t]);
4680 for (std::size_t t = 0; t < orders.size(); ++t) oj.push_back(orders[t]);
4682 for (std::size_t t = 0; t < b.size(); ++t) bj.push_back(b[t]);
4686 emit_analysis<T>(
"busyperiod", p,
"daduna");
4690 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t)
4691 std::printf(
"%s%zu", t ?
"," :
"", k.busy_subnet[t]);
4693 for (std::size_t t = 0; t < orders.size(); ++t)
4694 std::printf(
" order %zu %.10g\n", orders[t], b[t]);
4699int solve_model_normconst(
const std::string& file,
const Knobs& k,
const std::string& solver) {
4704 if (solver ==
"nc") {
4706 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4707 if (k.tol >= 0.0) opt.
tol = k.tol;
4708 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4709 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4712 : std::numeric_limits<double>::quiet_NaN();
4716 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4717 if (k.tol >= 0.0) opt.
tol = k.tol;
4718 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4719 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4723 if (g_json_output) {
4725 p[
"type"] =
"NormConst";
4728 p[
"logNormConstAggr"] = lG;
4729 emit_analysis<T>(
"normconst", p, method);
4732 std::printf(
"Solver%s arith=%s method=%s lognormconst=%.10g\n",
4742 for (std::size_t i = 0; i < sn.
nstations; ++i)
4743 for (std::size_t r = 0; r < sn.
nclasses; ++r)
4759int solve_model_nc_prob(
const std::string& file,
const Knobs& k) {
4762 "the -s nc -a prob analysis exponentiates a difference of log normalizing constants "
4763 "and needs transcendental arithmetic; rerun with --arith double or --arith real");
4767 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4768 if (k.tol >= 0.0) opt.
tol = k.tol;
4769 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4770 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4787 if (!k.state.empty()) {
4790 "--state is the state of ONE node and needs --node to say which");
4791 const std::size_t ist =
4792 k.node <= sn.
nodes.size() ? sn.
nodes[k.node - 1].station : 0;
4795 " is not a station, so it has no queue-length state");
4798 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4803 std::vector<T> row(k.state.size());
4804 for (std::size_t i = 0; i < k.state.size(); ++i)
4808 for (std::size_t c = 0; c < sn.
nclasses; ++c)
4827 sn, opt, nir, std::numeric_limits<double>::quiet_NaN());
4835 sn, opt, nir, std::numeric_limits<double>::quiet_NaN());
4843 if (g_json_output) {
4845 p[
"type"] =
"ProbAggr";
4849 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
4850 pm = line::reg::Json::array();
4851 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4859 emit_analysis<T>(
"prob", p, am);
4866 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
4867 for (std::size_t i = 0; i < sn.
nstations; ++i)
4868 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
4891int solve_model_nc_sysmarg(
const std::string& file,
const Knobs& k) {
4894 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4895 if (k.tol >= 0.0) opt.
tol = k.tol;
4899 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
4900 const double pop = sn.
classes[r].population;
4901 if (!std::isfinite(pop))
4903 "getProbSysMarg requires a closed model: the joint law of the total queue lengths "
4904 "is not defined when a class has an infinite population");
4908 static_cast<int>(sn.
nstations),
static_cast<int>(std::llround(Ntot)));
4910 std::vector<double> P(states.size(), 0.0);
4911 for (std::size_t j = 0; j < states.size(); ++j)
4915 if (g_json_output) {
4917 p[
"type"] =
"ProbSysMarg";
4919 p[
"engine"] = k.method_perm;
4920 line::reg::Json st = line::reg::Json::array(), arr = line::reg::Json::array();
4921 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
4922 for (std::size_t j = 0; j < states.size(); ++j) {
4925 for (std::size_t i = 0; i < sn.
nstations; ++i) n.push_back(states[j][i]);
4932 emit_analysis<T>(
"sysmarg", p, opt.
method);
4936 opt.
method.c_str(), k.method_perm.c_str());
4937 for (std::size_t i = 0; i < sn.
nstations; ++i)
4938 std::printf(
"%10s", sn.
stations[i].name.c_str());
4939 std::printf(
" %14s\n",
"ProbSysMarg");
4941 for (std::size_t j = 0; j < states.size(); ++j) {
4942 for (std::size_t i = 0; i < sn.
nstations; ++i) std::printf(
"%10d", states[j][i]);
4943 std::printf(
" %14.10g\n", P[j]);
4946 std::printf(
"%*s %14.10g\n",
static_cast<int>(10 * sn.
nstations),
"sum", total);
4968int solve_model_nc_marg(
const std::string& file,
const Knobs& k) {
4971 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4972 if (k.tol >= 0.0) opt.
tol = k.tol;
4973 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4974 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4977 std::vector<std::size_t> ists;
4981 " exceeds the number of nodes in the model (" +
4983 const std::size_t ist = sn.
nodes[k.node - 1].station;
4986 sn.
nodes[k.node - 1].name +
4987 "') is not a station, and a queue-length distribution is "
4988 "reported per station");
4989 ists.push_back(ist);
4991 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
4996 std::vector<line::nc::NcQueueLengthDist<T> > curves;
4997 for (std::size_t a = 0; a < ists.size(); ++a)
5000 if (g_json_output) {
5002 p[
"type"] =
"ProbMargAggr";
5005 for (std::size_t a = 0; a < ists.size(); ++a) {
5007 e[
"station"] = ists[a] - 1;
5008 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5009 e[
"P"] = vector_json<T>(curves[a].P);
5010 e[
"logP"] = vector_json<T>(curves[a].logP);
5014 emit_analysis<T>(
"marg", p, opt.
method);
5019 for (std::size_t a = 0; a < ists.size(); ++a) {
5020 std::printf(
"%-16s %-8s %14s %14s\n",
"Station",
"n",
"P",
"logP");
5021 for (std::size_t n = 0; n < curves[a].P.size(); ++n)
5022 std::printf(
"%-16s %-8zu %14.10g %14.10g\n", sn.
stations[ists[a] - 1].name.c_str(), n,
5045int solve_model_nc_cdf(
const std::string& file,
const Knobs& k) {
5048 "the -s nc -a cdf analysis evaluates the sojourn law on a logarithmic time grid and "
5049 "inverts a generating function; it needs transcendental arithmetic, so rerun with "
5050 "--arith double or --arith real");
5054 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5055 if (k.tol >= 0.0) opt.
tol = k.tol;
5056 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5057 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5058 if (!k.cdf_algorithm.empty()) opt.
cdf_algorithm = k.cdf_algorithm;
5061 if (!r.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", r.
warning.c_str());
5063 if (g_json_output) {
5065 p[
"type"] =
"CdfRespT";
5072 for (std::size_t i = 0; i < r.
RD.size(); ++i)
5073 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
5077 if (r.
RD[i][c].empty())
continue;
5079 e[
"Station"] = sn.
stations[i].name;
5080 e[
"JobClass"] = sn.
classes[c].name;
5083 line::reg::Json tt = line::reg::Json::array(), ff = line::reg::Json::array();
5084 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j) {
5093 p[
"tset"] = vector_json(r.
tset);
5098 emit_analysis<T>(
"cdf", p, std::string());
5103 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5104 for (std::size_t i = 0; i < r.
RD.size(); ++i)
5105 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
5106 if (r.
RD[i][c].empty())
continue;
5107 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j)
5108 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
5129double sens_sanitize_signed(
double x) {
5157int solve_model_nc_sens(
const std::string& file,
const Knobs& k) {
5160 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5161 if (k.tol >= 0.0) opt.
tol = k.tol;
5162 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5163 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5167 if (!k.sens_method.empty()) so.
method = k.sens_method;
5168 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
5169 if (k.sens_step > 0.0) so.
step = k.sens_step;
5175 sn, so,
true, [&sn, &opt]() {
5189 if (g_json_output) {
5191 p[
"type"] =
"SensitivityTable";
5193 p[
"branch"] = tbl.
method;
5211 emit_analysis<T>(
"sens", p, tbl.
method ==
"fd" ? opt.
method : std::string());
5218 std::printf(
"%-16s %-14s %14s %14s %14s %14s\n",
"Station",
"JobClass",
"dTput_dRate",
5219 "dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
5221 std::printf(
"%-16s %-14s %14.6g %14.6g %14.6g %14.6g\n", r.
station.c_str(),
5249 if (k.samples) o.
samples = k.samples;
5250 if (k.seed) o.
seed =
static_cast<long>(k.seed);
5251 if (!k.method.empty()) o.
method = k.method;
5252 if (!k.ldes_tranfilter.empty()) o.
tranfilter = k.ldes_tranfilter;
5253 if (k.ldes_warmupfrac >= 0.0) o.
warmupfrac = k.ldes_warmupfrac;
5254 if (!k.ldes_cimethod.empty()) o.
cimethod = k.ldes_cimethod;
5255 if (k.ldes_cnvgon) o.
cnvgon =
true;
5256 if (k.ldes_cnvgtol > 0.0) o.
cnvgtol = k.ldes_cnvgtol;
5257 if (k.ldes_slotted) o.
slotted =
true;
5258 if (k.ldes_slotlength > 0.0) {
5262 if (k.ldes_replications > 0) o.
replications = k.ldes_replications;
5263 if (k.ldes_numthreads > 0) o.
numthreads = k.ldes_numthreads;
5264 if (k.ldes_maxtime > 0.0) o.
timeout = k.ldes_maxtime;
5265 if (!k.ldes_initsol.empty()) o.
init_sol = k.ldes_initsol;
5266 if (!k.ldes_rest_url.empty()) o.
rest_url = k.ldes_rest_url;
5279inline std::string ldes_document(
const std::string& file) {
5280 return file.empty() ? stdin_model_text() :
line::ldes::detail::read_file(file);
5285 return i < M.
rows() && j < M.
cols() ? M(i, j) : 0.0;
5299 const std::vector<std::string>& extra) {
5303 "SolverLDES exceeded its wall-clock budget (--ldes-maxtime) and was terminated before "
5304 "it wrote a result; raise the budget or lower --samples");
5307 "SolverLDES: the engine reported no station names, so its metrics cannot be labelled; "
5308 "the run produced no result document");
5322 std::printf(
"SolverLDES arith=double method=%s type=%s engine=%s samples=%zu seed=%ld "
5323 "time=%.6g events=%lld stopping=%s\n",
5353int solve_model_ldes_avg(
const std::string& file,
const Knobs& k) {
5356 if (!g_json_output) ldes_banner(r, o);
5363 if (!r.
QNCI.
empty()) ci[
"QNCI"] = matrix_json<double>(r.
QNCI);
5364 if (!r.
UNCI.
empty()) ci[
"UNCI"] = matrix_json<double>(r.
UNCI);
5365 if (!r.
RNCI.
empty()) ci[
"RNCI"] = matrix_json<double>(r.
RNCI);
5366 if (!r.
TNCI.
empty()) ci[
"TNCI"] = matrix_json<double>(r.
TNCI);
5367 if (!r.
ANCI.
empty()) ci[
"ANCI"] = matrix_json<double>(r.
ANCI);
5368 if (!r.
WNCI.
empty()) ci[
"WNCI"] = matrix_json<double>(r.
WNCI);
5369 if (!ci.empty()) extra[
"CI"] = ci;
5373 f[
"QNfcr"] = matrix_json<double>(r.
QNfcr);
5374 f[
"RNfcr"] = matrix_json<double>(r.
RNfcr);
5375 f[
"TNfcr"] = matrix_json<double>(r.
TNfcr);
5376 f[
"WNfcr"] = matrix_json<double>(r.
WNfcr);
5385 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
5389 if (!it->second.hit.empty()) c[
"hit"] = matrix_json<double>(it->second.hit);
5390 if (!it->second.delayed.empty()) c[
"delayed"] = matrix_json<double>(it->second.delayed);
5391 if (!it->second.miss.empty()) c[
"miss"] = matrix_json<double>(it->second.miss);
5392 if (!it->second.latency.empty()) c[
"latency"] = matrix_json<double>(it->second.latency);
5393 if (!it->second.hitList.empty()) c[
"hitList"] = matrix_json<double>(it->second.hitList);
5394 if (!it->second.itemProb.empty())
5395 c[
"itemProb"] = matrix_json<double>(it->second.itemProb);
5396 if (!it->second.listCost.empty())
5397 c[
"listCost"] = matrix_json<double>(it->second.listCost);
5400 extra[
"cacheMetrics"] = cm;
5421 for (std::size_t j = 0; j < sn.
nstations; ++j)
5423 std::vector<std::size_t> cl_of(r.
class_names.size(), 0);
5424 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
5425 for (std::size_t k = 0; k < sn.
nclasses; ++k)
5429 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
5430 if (st_of[i] && cl_of[c]) RNs(st_of[i] - 1, cl_of[c] - 1) = ldes_at(r.
RN, i, c);
5433 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
5434 WNd(i, c) = (st_of[i] && cl_of[c]) ? WNs(st_of[i] - 1, cl_of[c] - 1)
5435 : ldes_at(r.
WN, i, c);
5439 [&](std::size_t i, std::size_t c) {
5441 v.q = ldes_at(r.QN, i, c);
5442 v.u = ldes_at(r.UN, i, c);
5443 v.r = ldes_at(r.RN, i, c);
5445 v.a = ldes_at(r.AN, i, c);
5446 v.t = ldes_at(r.TN, i, c);
5449 extra, ldes_envelope(r, o));
5452 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s\n",
"Region",
"JobClass",
"QLen",
"RespT",
5453 "Tput",
"Weight",
"MemOcc");
5454 for (std::size_t i = 0; i < r.
QNfcr.
rows(); ++i)
5455 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
5456 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
5457 (
"Region" + std::to_string(i + 1)).c_str(), r.
class_names[c].c_str(),
5458 ldes_at(r.
QNfcr, i, c), ldes_at(r.
RNfcr, i, c),
5463 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Cache",
"JobClass",
"Hit",
"Delayed",
5465 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
5468 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
5469 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", it->first.c_str(),
5470 r.
class_names[c].c_str(), ldes_at(it->second.hit, 0, c),
5471 ldes_at(it->second.delayed, 0, c), ldes_at(it->second.miss, 0, c),
5472 ldes_at(it->second.latency, 0, c));
5491int solve_model_ldes_tran(
const std::string& file,
const Knobs& k) {
5496 std::vector<std::string> extra;
5497 extra.push_back(
"--trajectory");
5499 if (r.
t.empty() || r.
QNt.empty())
5501 "SolverLDES -a tran produced no trajectory: the engine ran but recorded no bucket over "
5502 "[" + line::ldes::detail::shortest(k.t0) +
"," +
5503 line::ldes::detail::shortest(k.t1) +
"]");
5505 if (g_json_output) {
5507 p[
"type"] =
"TranAvgTable";
5512 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
5513 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
5516 if (r.
QNt[i][c].empty())
continue;
5519 :
"Station" + std::to_string(i);
5524 line::reg::Json tt = line::reg::Json::array(), q = line::reg::Json::array(),
5525 u = line::reg::Json::array(), x = line::reg::Json::array();
5526 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
5527 tt.push_back(r.
QNt[i][c](j, 1));
5528 q.push_back(r.
QNt[i][c](j, 0));
5530 if (i < r.
UNt.size() && c < r.
UNt[i].size())
5531 for (std::size_t j = 0; j < r.
UNt[i][c].rows(); ++j)
5532 u.push_back(r.
UNt[i][c](j, 0));
5533 if (i < r.
TNt.size() && c < r.
TNt[i].size())
5534 for (std::size_t j = 0; j < r.
TNt[i][c].rows(); ++j)
5535 x.push_back(r.
TNt[i][c](j, 0));
5540 curves.push_back(e);
5542 p[
"curves"] = curves;
5543 p[
"tset"] = vector_json(r.
t);
5548 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
5549 p[it.key()] = it.value();
5550 emit_analysis<double>(
"tran", p, r.
method);
5554 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
5556 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
5557 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
5558 if (r.
QNt[i][c].empty())
continue;
5559 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
5560 const bool hu = i < r.
UNt.size() && c < r.
UNt[i].size() &&
5561 j < r.
UNt[i][c].rows();
5562 const bool hx = i < r.
TNt.size() && c < r.
TNt[i].size() &&
5563 j < r.
TNt[i][c].rows();
5564 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
5567 r.
QNt[i][c](j, 1), r.
QNt[i][c](j, 0),
5568 hu ? r.
UNt[i][c](j, 0) : 0.0, hx ? r.
TNt[i][c](j, 0) : 0.0);
5587int solve_model_ldes_cdf(
const std::string& file,
const Knobs& k,
const char* key,
5590 std::vector<std::string> extra;
5591 extra.push_back(
"--respt-samples");
5595 "SolverLDES -a cdf needs the per-job response times the engine records under "
5596 "--respt-samples and the run returned none; raise --samples so completions are "
5601 std::vector<std::vector<std::vector<double>>> tt(r.
respTimeSamples.size()), ff(
5608 if (x.empty())
continue;
5609 std::sort(x.begin(), x.end());
5610 const double n =
static_cast<double>(x.size());
5611 for (std::size_t j = 0; j < x.size(); ++j) {
5612 if (j + 1 < x.size() && x[j + 1] == x[j])
continue;
5613 tt[i][c].push_back(x[j]);
5614 ff[i][c].push_back(
static_cast<double>(j + 1) / n);
5619 if (g_json_output) {
5623 p[
"algorithm"] =
"empirical";
5625 for (std::size_t i = 0; i < tt.size(); ++i)
5626 for (std::size_t c = 0; c < tt[i].size(); ++c) {
5627 if (tt[i][c].empty())
continue;
5630 :
"Station" + std::to_string(i);
5635 e[
"t"] = vector_json(tt[i][c]);
5636 e[
"F"] = vector_json(ff[i][c]);
5641 emit_analysis<double>(key, p, std::string());
5645 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5646 for (std::size_t i = 0; i < tt.size(); ++i)
5647 for (std::size_t c = 0; c < tt[i].size(); ++c)
5648 for (std::size_t j = 0; j < tt[i][c].size(); ++j)
5649 std::printf(
"%-16s %-14s %14.8g %14.10g\n",
5652 tt[i][c][j], ff[i][c][j]);
5669int solve_model_ldes_sample(
const std::string& file,
const Knobs& k) {
5674 std::vector<std::string> extra;
5675 extra.push_back(
"--trajectory");
5677 if (r.
t.empty() || r.
QNt.empty())
5679 "SolverLDES -a sample produced no trajectory over [0," +
5680 line::ldes::detail::shortest(o.
t1) +
"]");
5682 const std::size_t M = r.
QNt.size(), K = r.
class_names.size(), n = r.
t.size();
5683 if (g_json_output) {
5685 p[
"type"] =
"SamplePath";
5687 p[
"scope"] =
"(system)";
5689 p[
"t"] = vector_json(r.
t);
5691 for (std::size_t j = 0; j < n; ++j) {
5693 for (std::size_t i = 0; i < M; ++i)
5694 for (std::size_t c = 0; c < K; ++c)
5695 row.push_back(c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
5702 for (std::size_t i = 0; i < M; ++i)
5703 for (std::size_t c = 0; c < K; ++c)
5706 p[
"columns"] = cols;
5711 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
5712 p[it.key()] = it.value();
5713 emit_analysis<double>(
"sample", p, r.
method);
5717 std::printf(
"%14s %s\n",
"Time",
"SysState (station-major, per class)");
5718 for (std::size_t j = 0; j < n; ++j) {
5719 std::printf(
"%14.8g ", r.
t[j]);
5720 for (std::size_t i = 0; i < M; ++i)
5721 for (std::size_t c = 0; c < K; ++c)
5722 std::printf(
" %g", c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
5744int solve_model_ldes_reward(
const std::string& file,
const Knobs& k) {
5749 "-s ldes -a reward needs a reward declared on the model (set_reward(name, fn), the "
5750 "`rewards` block of model.json); there is nothing to average");
5753 std::vector<std::string> extra;
5754 extra.push_back(
"--export-histogram");
5758 "SolverLDES -a reward needs the joint-state residence-time histogram the engine "
5759 "exports under --export-histogram and the run returned none");
5766 "SolverLDES -a reward: the state histogram carries no residence time, so no state "
5767 "distribution can be formed from it");
5770 std::vector<double> E(sn.
reward.size(), 0.0);
5771 std::vector<std::string> names(sn.
reward.size());
5772 for (std::size_t l = 0; l < sn.
reward.size(); ++l) {
5773 names[l] = sn.
reward[l].name;
5774 for (std::size_t s = 0; s < ns; ++s) {
5775 std::vector<double> row(w);
5780 E[l] += (t / total) * sn.
reward[l].fn(row);
5784 if (g_json_output) {
5786 p[
"type"] =
"AvgReward";
5788 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
5790 p[
"E"] = vector_json(E);
5796 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
5797 p[it.key()] = it.value();
5800 emit_analysis<double>(
"reward", p, std::string());
5804 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
5805 for (std::size_t l = 0; l < E.size(); ++l)
5806 std::printf(
"%-28s %16.10g\n", names[l].c_str(), E[l]);
5821int solve_model_ldes(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
5822 if (analysis ==
"avg")
return solve_model_ldes_avg(file, k);
5823 if (analysis ==
"tran")
return solve_model_ldes_tran(file, k);
5831 if (analysis ==
"cdf")
return solve_model_ldes_cdf(file, k,
"cdf",
"CdfRespT");
5832 if (analysis ==
"cdfpasst")
return solve_model_ldes_cdf(file, k,
"cdfpasst",
"CdfPassT");
5833 if (analysis ==
"trancdf")
return solve_model_ldes_cdf(file, k,
"trancdf",
"TranCdfRespT");
5834 if (analysis ==
"trancdfpasst")
5835 return solve_model_ldes_cdf(file, k,
"trancdfpasst",
"TranCdfPassT");
5836 if (analysis ==
"sample")
return solve_model_ldes_sample(file, k);
5837 if (analysis ==
"reward")
return solve_model_ldes_reward(file, k);
5838 if (analysis ==
"prob")
5840 "-s ldes -a prob is not ported: getProb/getProbSys weigh the trajectory by the time "
5841 "spent in the model's CURRENT state, and the C++ NetworkStruct carries no such state "
5842 "row to compare against (only a Cache initstate, a Place initmarking and the "
5843 "statePrior/space pair). Use -s ctmc -a prob for an exact marginal, or -s ssa -a prob "
5844 "for a simulated one");
5846 "SolverLDES ports -a avg (getAvg) and its four views -a node, -a sys, -a chain and "
5847 "-a nodechain, -a tran (getTranAvg), -a cdf / cdf-passt / "
5848 "tran-cdf-respt / tran-cdf-passt (the empirical passage law, one measurement under the "
5849 "four names the reference gives it), -a sample (sampleSys) and -a reward "
5850 "(getAvgReward); got '" + analysis +
"'");
5853std::string auto_getter_of_analysis(
const std::string& analysis) {
5854 if (analysis ==
"prob")
return "getProbSysAggr";
5855 if (analysis ==
"marg")
return "getProbMarg";
5856 if (analysis ==
"sysmarg")
return "getProbSysMarg";
5857 if (analysis ==
"normconst")
return "getProbNormConstAggr";
5858 if (analysis ==
"tranprob")
return "getTranProbSysAggr";
5859 if (analysis ==
"sample")
return "sampleSys";
5860 if (analysis ==
"cdf")
return "getCdfRespT";
5861 if (analysis ==
"gen")
return "getInfGen";
5862 if (analysis ==
"states")
return "getStateSpace";
5863 if (analysis ==
"reward")
return "getAvgReward";
5864 if (analysis ==
"sens")
return "getSensitivityTable";
5865 if (analysis ==
"tran")
return "getTranAvg";
5866 if (analysis ==
"internals")
return "getMAMResult";
5872 if (analysis ==
"node")
return "getAvgNodeTable";
5873 return "getAvgTable";
5881 std::vector<std::string> order;
5887std::string auto_cli_token_of_family(
const std::string& fam) {
5888 if (fam ==
"mva" || fam ==
"nc" || fam ==
"ctmc" || fam ==
"mam" || fam ==
"ag" ||
5889 fam ==
"ssa" || fam ==
"ba" || fam ==
"uq" || fam ==
"env")
5891 if (fam ==
"fluid")
return "fluid";
5892 if (fam ==
"ldes") {
5898 "--method ldes names the discrete-event engine, and no engine was found beside "
5899 "this binary (common/ldes or common/ldes.jar, or $LINE_LDES_DIR); -s ssa is the "
5900 "simulator this port builds in process");
5912 if (fam ==
"qns")
return "qns";
5915 "--method lqns names the external layered binary, which solves a LayeredNetwork and "
5916 "not a Network; reach it as -i lqnx -s lqns");
5919 "--method ln names the layered solver, which takes a LayeredNetwork: pass the model "
5920 "as -i lqnx -s ln rather than as a Network");
5921 throw line::InputError(
"SolverAUTO: no engine stands behind method family '" + fam +
"'");
5932bool model_is_environment(
const std::string& file) {
5934 line::io::detail::json root;
5936 std::istringstream in(stdin_model_text());
5939 std::ifstream in(file.c_str());
5940 if (!in)
return false;
5946 if (!root.is_object())
return false;
5947 const line::io::detail::json& model = root.contains(
"model") ? root.at(
"model") : root;
5948 return model.is_object() && model.value(
"type", std::string()) ==
"Environment";
5974AutoPlan choose_auto_plan(
const std::string& file,
const std::string& analysis,
5975 const std::string& method_token) {
5979 plan.order.push_back(auto_cli_token_of_family(tok.
family));
5984 const std::string getter = auto_getter_of_analysis(analysis);
5985 if (model_is_environment(file)) {
5988 plan.order.push_back(
"env");
5989 for (std::size_t i = 0; i < ec.
skipped.size(); ++i)
5990 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
5992 if (!ec.
skipped.empty()) plan.note +=
", which this port does not build)";
6000 const std::vector<line::autosolver::AutoSolver> proposed =
6002 for (std::size_t i = 0; i < proposed.size(); ++i)
6004 for (std::size_t i = 0; i < c.
skipped.size(); ++i)
6005 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
6007 if (!c.
skipped.empty()) plan.note +=
", which this port does not build)";
6011AutoPlan choose_auto_plan_dispatch(
const std::string& arith,
const std::string& file,
6012 const std::string& analysis,
const std::string& method_token) {
6013 if (arith ==
"exact")
return choose_auto_plan<line::Rational>(file, analysis, method_token);
6014 if (arith ==
"real:16")
return choose_auto_plan<line::Real<16> >(file, analysis, method_token);
6015 if (arith ==
"real" || arith ==
"real:32")
6016 return choose_auto_plan<line::Real<32> >(file, analysis, method_token);
6017 if (arith ==
"real:64")
return choose_auto_plan<line::Real<64> >(file, analysis, method_token);
6018 if (arith ==
"real:128")
6019 return choose_auto_plan<line::Real<128> >(file, analysis, method_token);
6020 if (arith ==
"real:256")
6021 return choose_auto_plan<line::Real<256> >(file, analysis, method_token);
6022 return choose_auto_plan<double>(file, analysis, method_token);
6036double ln_sanitize(
double x) {
6037 const double r = std::round(x * 10.0);
6044 switch (l.
type[i]) {
6048 default:
return "Activity";
6064 using namespace line;
6065 const std::vector<qn::Layer<T> >& ens = solver.
layers();
6066 for (std::size_t k = 0; k < ens.size(); ++k) {
6068 std::printf(
"LAYER %zu %s nstations=%zu nclasses=%zu nchains=%zu\n", k + 1, L.
name.c_str(),
6070 for (std::size_t i = 0; i < L.
stations.size(); ++i)
6071 std::printf(
" STATION %zu %s sched=%s nservers=%g\n", i + 1, L.
stations[i].name.c_str(),
6073 for (std::size_t r = 0; r < L.
classes.size(); ++r)
6074 std::printf(
" CLASS %zu %s pop=%.17g refstat=%zu completes=%d\n", r + 1,
6077 for (std::size_t i = 0; i < L.
stations.size(); ++i)
6078 for (std::size_t r = 0; r < L.
classes.size(); ++r) {
6080 std::printf(
" RATE %s %s %.17g scv=%.17g\n", L.
stations[i].name.c_str(),
6084 for (
const auto& kv : L.
P) {
6086 for (std::size_t i = 0; i < B.
rows(); ++i)
6087 for (std::size_t j = 0; j < B.
cols(); ++j) {
6089 if (p == 0.0)
continue;
6090 std::printf(
" ROUTE %s->%s %s->%s %.17g\n",
6091 L.
classes[kv.first.first - 1].name.c_str(),
6092 L.
classes[kv.first.second - 1].name.c_str(),
6093 L.
nodes[i].name.c_str(), L.
nodes[j].name.c_str(), p);
6112 using namespace line;
6117 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
6118 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
6119 if (k.no_interlocking)
opt.interlocking =
false;
6120 if (!k.layer_solver.empty())
opt.layer_solver = k.layer_solver;
6121 if (!k.method.empty())
opt.method = k.method;
6122 if (k.samples)
opt.layer_ssa.samples = k.samples;
6123 if (k.seed)
opt.layer_ssa.seed = k.seed;
6124 if (k.t1 >= 0.0)
opt.timespan_end = k.t1;
6125 if (k.tran_points)
opt.tran_points = k.tran_points;
6126 if (!k.ln_transient.empty())
opt.ln_transient = k.ln_transient;
6127 if (!k.ln_transient_channels.empty())
opt.ln_transient_channels = k.ln_transient_channels;
6132inline const char* ln_layer_engine_name(
const std::string& layer_solver) {
6133 if (layer_solver ==
"fluid")
return "Fluid";
6134 if (layer_solver ==
"nc")
return "NC";
6135 if (layer_solver ==
"ssa")
return "SSA";
6147int run_ln_tran(
const std::string& file,
const std::string& output,
const Knobs& k) {
6148 using namespace line;
6153 const std::vector<qn::Layer<T>>& layers = solver.
layers();
6155 if (output ==
"json") {
6159 j[
"mode"] =
tr.mode;
6160 j[
"iterations"] =
tr.iterations;
6161 j[
"gap"] = ln_sanitize(
tr.gap);
6163 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
6165 le[
"layer"] = layers[e].name;
6167 for (
double t :
tr.layers[e].t) tt.push_back(ln_sanitize(t));
6170 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
6171 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
6173 s[
"station"] = layers[e].stations[i].name;
6174 s[
"class"] = layers[e].classes[r].name;
6175 auto arr = [&](
const std::vector<double>& v) {
6177 for (
double x : v) a.push_back(ln_sanitize(x));
6180 s[
"QLen"] = arr(
tr.layers[e].QN[i][r]);
6181 s[
"Util"] = arr(
tr.layers[e].UN[i][r]);
6182 s[
"Tput"] = arr(
tr.layers[e].TN[i][r]);
6183 series.push_back(s);
6185 le[
"series"] = series;
6189 std::printf(
"%s\n", j.dump(1).c_str());
6193 std::printf(
"SolverLN(Solver%s) getTranAvg arith=%s mode=%s layers=%zu iterations=%ld gap=%.3e\n",
6195 tr.layers.size(),
tr.iterations,
tr.gap);
6196 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
6197 const std::vector<double>& t =
tr.layers[e].t;
6198 if (t.empty())
continue;
6199 std::printf(
"\nLayer %s (%zu points on [%.6g, %.6g])\n", layers[e].name.c_str(), t.size(),
6200 t.front(), t.back());
6201 std::printf(
"%-30s %-24s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen(0)",
6202 "QLen(end)",
"Util(end)",
"Tput(end)");
6203 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
6204 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
6205 const std::vector<double>& q =
tr.layers[e].QN[i][r];
6206 if (q.empty())
continue;
6207 std::printf(
"%-30s %-24s %12.6g %12.6g %12.6g %12.6g\n",
6208 layers[e].stations[i].name.c_str(), layers[e].classes[r].name.c_str(),
6209 ln_sanitize(q.front()), ln_sanitize(q.back()),
6210 ln_sanitize(
tr.layers[e].UN[i][r].back()),
6211 ln_sanitize(
tr.layers[e].TN[i][r].back()));
6219int run_ln_sens(
const std::string& file,
const std::string& output,
const Knobs& k) {
6220 using namespace line;
6225 if (!k.sens_method.empty()) so.
method = k.sens_method;
6226 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
6227 if (k.sens_step > 0.0) so.
step = k.sens_step;
6230 if (output ==
"json") {
6234 j[
"method"] = tbl.
method;
6236 for (
const auto& r : tbl.
rows) {
6238 o[
"Layer"] = r.layer;
6239 o[
"Station"] = r.station;
6240 o[
"JobClass"] = r.jobclass;
6248 std::printf(
"%s\n", j.dump(1).c_str());
6252 std::printf(
"SolverLN(Solver%s) getSensitivityTable arith=%s method=%s rows=%zu\n",
6255 std::printf(
"%-28s %-28s %-20s %14s %14s %14s %14s\n",
"Layer",
"Station",
"JobClass",
6256 "dTput_dRate",
"dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
6257 for (
const auto& r : tbl.
rows)
6258 std::printf(
"%-28s %-28s %-20s %14.6g %14.6g %14.6g %14.6g\n", r.layer.c_str(),
6259 r.station.c_str(), r.jobclass.c_str(),
6269int run_ln_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
6270 using namespace line;
6276 opt.method =
"moment3";
6281 if (output ==
"json") {
6286 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
6288 o[
"entry"] = names.names[model.
eshift + e];
6289 reg::Json tt = reg::Json::array(), ff = reg::Json::array();
6290 for (std::size_t p = 0; p < cdf[e].t.size(); ++p) {
6291 tt.push_back(ln_sanitize(cdf[e].t[p]));
6292 ff.push_back(ln_sanitize(cdf[e].cdf[p]));
6298 j[
"entries"] = rows;
6299 std::printf(
"%s\n", j.dump(1).c_str());
6303 std::printf(
"SolverLN(Solver%s) getCdfRespT arith=%s method=moment3 entries=%zu\n",
6305 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
6306 if (cdf[e].t.empty()) {
6307 std::printf(
"%-40s (no distribution: the entry has no fitted term)\n",
6308 names.names[model.
eshift + e].c_str());
6312 auto quantile = [&](
double p) {
6313 for (std::size_t i = 0; i < cdf[e].cdf.size(); ++i)
6314 if (cdf[e].cdf[i] >= p)
return cdf[e].t[i];
6315 return cdf[e].t.back();
6317 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g points=%zu\n",
6318 names.names[model.
eshift + e].c_str(), ln_sanitize(quantile(0.25)),
6319 ln_sanitize(quantile(0.50)), ln_sanitize(quantile(0.75)),
6320 ln_sanitize(quantile(0.95)), cdf[e].t.size());
6326int run_ln(
const std::string& file,
const std::string& output,
const Knobs& k) {
6327 using namespace line;
6331 const int repeat = k.repeat > 0 ? k.repeat : 1;
6333 double best = 1e300;
6335 std::size_t nlayers = 0;
6336 for (
int rep = 0; rep < repeat; ++rep) {
6337 const auto t0 = std::chrono::steady_clock::now();
6341 if (output ==
"layers") {
6342 ln_dump_layers(solver);
6345 const auto t1 = std::chrono::steady_clock::now();
6346 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
6352 if (output ==
"json") {
6356 j[
"layers"] = nlayers;
6359 j[
"seconds"] = best;
6361 for (std::size_t i = 1; i <= model.
nidx; ++i) {
6363 r[
"node"] = names.names[i];
6364 r[
"type"] = ln_element_kind(names, i);
6365 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
6367 else if (sol.
is_bound) r[key] = 0.0;
6368 else r[key] =
nullptr;
6378 std::printf(
"%s\n", j.dump(1).c_str());
6386 "SolverLN(Solver%s) arith=%s type=%s layers=%zu iterations=%d converged=%d time=%.4fs\n",
6390 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
6391 "RespT",
"ResidT",
"Tput");
6392 for (std::size_t i = 1; i <= model.
nidx; ++i) {
6405 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
6406 if (!d[i] && sol.
is_bound) std::snprintf(buf, 24,
"%12.6g", 0.0);
6407 else if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
6410 char q[24], u[24], rr[24], w[24], t[24];
6416 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
6417 ln_element_kind(names, i), q, u, rr, w, t);
6465int run_ln_ldes_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
6466 using namespace line;
6469 if (k.samples) o.
samples = k.samples;
6470 if (k.seed) o.
seed =
static_cast<long>(k.seed);
6476 const std::vector<ldes::engine::LnEntryCdf> cdf =
6479 for (std::size_t e = 0; e < model.
nentries; ++e) {
6484 if (output ==
"json") {
6487 j[
"engine"] =
"native-ln";
6489 for (std::size_t e = 0; e < model.
nentries; ++e) {
6492 reg::Json at = reg::Json::array(), af = reg::Json::array();
6493 for (std::size_t i = 0; i < tt[e].size(); ++i) {
6494 at.push_back(tt[e][i]);
6495 af.push_back(ff[e][i]);
6499 ob[
"observations"] =
static_cast<double>(
6503 j[
"entries"] = rows;
6504 std::printf(
"%s\n", j.dump(1).c_str());
6508 std::printf(
"SolverLDES(native LN engine) getCdfRespT entries=%zu\n", model.
nentries);
6509 for (std::size_t e = 0; e < model.
nentries; ++e) {
6510 if (tt[e].empty()) {
6511 std::printf(
"%-40s (no observation)\n", model.
names[model.
eshift + e + 1].c_str());
6516 const std::vector<double>& t;
6517 const std::vector<double>& f;
6518 double operator()(
double p)
const {
6519 for (std::size_t i = 0; i < f.size(); ++i)
6520 if (f[i] >= p)
return t[i];
6523 } q = {tt[e], ff[e]};
6524 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g n=%zu\n",
6525 model.
names[model.
eshift + e + 1].c_str(), q(0.25), q(0.50), q(0.75),
6531int run_ln_ldes(
const std::string& file,
const std::string& output,
const Knobs& k) {
6532 using namespace line;
6539 if (k.samples) o.
samples = k.samples;
6540 if (k.seed) o.
seed =
static_cast<long>(k.seed);
6547 const int repeat = k.repeat > 0 ? k.repeat : 1;
6548 double best = 1e300;
6550 for (
int rep = 0; rep < repeat; ++rep) {
6551 const auto t0 = std::chrono::steady_clock::now();
6553 const auto t1 = std::chrono::steady_clock::now();
6554 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
6562 const auto def = [&](std::size_t i, Col c) {
6566 if (output ==
"json") {
6569 j[
"arith"] =
"double";
6570 j[
"solver"] =
"ldes";
6575 j[
"engine"] =
"native-ln";
6580 j[
"seconds"] = best;
6582 for (std::size_t i = 1; i <= model.
nidx; ++i) {
6584 row[
"node"] = model.
names[i];
6585 row[
"type"] = ln_element_kind(model, i);
6586 auto put = [&](
const char* key,
double v,
bool defined) {
6587 if (defined) row[key] = v;
6588 else row[key] =
nullptr;
6590 put(
"QLen", r.
QLN(i, 0), def(i, Col::QLen));
6591 put(
"Util", r.
ULN(i, 0), def(i, Col::Util));
6592 put(
"RespT", r.
RLN(i, 0), def(i, Col::RespT));
6593 put(
"ResidT", r.
WLN(i, 0), def(i, Col::ResidT));
6594 put(
"Tput", r.
TLN(i, 0), def(i, Col::Tput));
6595 rows.push_back(row);
6598 std::printf(
"%s\n", j.dump(1).c_str());
6602 std::printf(
"SolverLDES(native LN engine) arith=double type=%s samples=%zu seed=%ld "
6603 "simtime=%.6g completions=%lld time=%.4fs\n",
6606 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
6607 "RespT",
"ResidT",
"Tput");
6608 for (std::size_t i = 1; i <= model.
nidx; ++i) {
6610 auto fmt = [&](
double v,
bool defined,
char* buf) {
6611 if (!defined) std::snprintf(buf, 24,
"%12s",
"NaN");
6612 else std::snprintf(buf, 24,
"%12.6g", v);
6614 char q[24], u[24], rr[24], w[24], t[24];
6615 fmt(r.
QLN(i, 0), def(i, Col::QLen), q);
6616 fmt(r.
ULN(i, 0), def(i, Col::Util), u);
6617 fmt(r.
RLN(i, 0), def(i, Col::RespT), rr);
6618 fmt(r.
WLN(i, 0), def(i, Col::ResidT), w);
6619 fmt(r.
TLN(i, 0), def(i, Col::Tput), t);
6620 std::printf(
"%-62s %-10s %s %s %s %s %s\n", model.
names[i].c_str(),
6621 ln_element_kind(model, i), q, u, rr, w, t);
6638int run_lqns(
const std::string& file,
const std::string& output,
const Knobs& k) {
6639 using namespace line;
6643 if (!k.method.empty())
opt.method = k.method;
6644 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
6645 if (k.samples)
opt.samples =
static_cast<double>(k.samples);
6646 opt.verbose = k.verbose;
6648 opt.remote = k.remote;
6649 if (!k.remote_url.empty())
opt.remote_url = k.remote_url;
6650 opt.timeout_seconds = k.timeout_seconds;
6657 if (output ==
"json") {
6662 j[
"method"] =
opt.method;
6664 j[
"seconds"] = solver.runtime();
6666 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
6668 r[
"node"] = names.names[i];
6669 r[
"type"] = ln_element_kind(names, i);
6670 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
6672 else r[key] =
nullptr;
6682 std::printf(
"%s\n", j.dump(1).c_str());
6686 std::printf(
"SolverLQNS(%s) arith=%s type=%s iterations=%d time=%.4fs\n",
6690 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
6691 "RespT",
"ResidT",
"Tput");
6692 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
6694 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
6695 if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
6697 std::snprintf(buf, 24,
"%12.6g",
6700 char q[24], u[24], rr[24], w[24], t[24];
6706 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
6707 ln_element_kind(names, i), q, u, rr, w, t);
6733std::string upper_tag(
const std::string& s) {
6734 std::string out = s;
6735 for (std::size_t i = 0; i < out.size(); ++i)
6736 out[i] =
static_cast<char>(std::toupper(
static_cast<unsigned char>(out[i])));
6740int solve_lqn_dispatch(
const std::string& arith,
const std::string& solver,
6741 const std::string& analysis,
const std::string& output,
6742 const std::string& file,
const Knobs& k) {
6745 "a layered model is read from a file: pass -f <model.lqnx> or -f <model.json> (neither "
6746 "layered reader has a stdin form)");
6747 if (analysis !=
"avg" && analysis !=
"tran" && analysis !=
"sens" && analysis !=
"cdf")
6749 "the layered path ports -a avg (getAvgTable), -a tran (getTranAvg), -a sens "
6750 "(getSensitivityTable) and -a cdf (getCdfRespT); got '" + analysis +
"'");
6751 const std::string s = solver.empty() ?
"auto" : solver;
6752 if (s !=
"auto" && s !=
"ln" && s !=
"ln.mva" && s !=
"ln.comom" && s !=
"lqns" &&
6755 "the layered path takes -s ln, ln.mva, ln.comom, ldes, lqns or auto (got '" + s +
6756 "'); a Network solver cannot be applied to a LayeredNetwork directly");
6765 std::string engine = s;
6769 if (s ==
"auto" && kk.layer_solver.empty()) {
6770 std::string getter =
"getAvgTable";
6771 if (analysis ==
"tran") getter =
"getTranAvg";
6772 else if (analysis ==
"cdf") getter =
"getCdfRespT";
6773 else if (analysis ==
"sens") getter =
"getSensitivityTable";
6777 bool has_cache_task =
false;
6778 for (std::size_t i = 0; i < probe.
iscache.size(); ++i)
6779 if (probe.
iscache[i]) has_cache_task =
true;
6783 if (token ==
"lqns") engine =
"lqns";
6784 else if (token ==
"ln.comom") kk.layer_solver =
"nc";
6785 else if (token ==
"ln.fluid") kk.layer_solver =
"fluid";
6786 else kk.layer_solver =
"mva";
6788 for (std::size_t i = 0; i < lc.
skipped.size(); ++i)
6789 note += std::string(i ?
", " :
" (the ranking preferred ") +
6791 if (!lc.
skipped.empty()) note +=
", which is not available here)";
6792 std::printf(
"SolverAUTO selected %s%s\n", token.c_str(), note.c_str());
6800 if (engine ==
"lqns") {
6801 if (analysis !=
"avg")
6803 "SolverLQNS reports the mean table its binary computes; it has no transient, no "
6804 "sensitivity and no response-time distribution here, so it takes -a avg (got '" +
6806 if (!kk.layer_solver.empty())
6808 "--layer-solver names the engine SolverLN runs on each layer; -s lqns solves no "
6809 "layers, it hands the whole model to the lqns binary");
6810 if (output ==
"layers")
6812 "-o layers dumps the stations and routing SolverLN BUILT from the model; lqns "
6813 "builds its own submodels inside another process and this port never sees them");
6814 if (kk.iter_tol >= 0.0 || kk.iter_max > 0)
6816 "--iter_tol and --iter_max are SolverLN's layer-iteration knobs; lqns runs its own "
6817 "iteration and takes neither (its --iteration-limit is unreliable as of 6.2.27, "
6818 "which is why the reference stopped passing it)");
6821 "--seed sets the stream of a simulator this port drives; lqsim seeds itself and "
6822 "the wrapper passes no seed, exactly as the reference does not");
6825 "--repeat times a solve by re-running it; re-running lqsim would report a "
6826 "different answer under the same banner");
6827 if (kk.no_interlocking || !kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
6828 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0)
6830 "--no-interlocking, --ln-transient*, and --sens-* are SolverLN options; -s lqns "
6831 "has none of them");
6832 if (arith !=
"double")
6834 "SolverLQNS reads a result file another program wrote in decimal double "
6835 "precision; there is no higher precision to carry, so rerun with --arith double "
6836 "(got '" + arith +
"')");
6843 if (kk.has_cutoff())
6845 "--cutoff bounds the open population of a CTMC state space and applies to -s "
6846 "ctmc; the layered path enumerates no states");
6849 "--node selects the stateful node a CTMC query is labelled by; the layered path "
6850 "reports every LQN element");
6851 return run_lqns<double>(file, output, kk);
6853 if (k.keep || k.verbose || k.remote || !k.remote_url.empty() || k.timeout_seconds)
6855 "--keep, --verbose, --remote, --remote-url and --timeout describe the child process "
6856 "of an external solver and apply to -s lqns only");
6863 if (engine ==
"ldes") {
6864 if (analysis !=
"avg" && analysis !=
"cdf")
6866 "the native LN engine measures a sample path: it takes -a avg for the mean table "
6867 "and -a cdf for the per-entry response time distribution, and has no transient "
6868 "and no sensitivity here (got '" + analysis +
"')");
6869 if (arith !=
"double")
6871 "the native LN engine accumulates its estimators in double, so there is no higher "
6872 "precision to carry; rerun with --arith double (got '" + arith +
"')");
6873 if (output ==
"layers")
6875 "-o layers dumps the stations and routing SolverLN BUILT from the model; -s ldes "
6876 "simulates the layered semantics directly and builds no submodels");
6877 if (!kk.layer_solver.empty())
6879 "--layer-solver names the engine SolverLN runs on each layer; -s ldes solves no "
6880 "layers, it simulates entries, activities and calls directly");
6881 if (kk.iter_tol >= 0.0 || kk.iter_max > 0 || kk.no_interlocking)
6883 "--iter_tol, --iter_max and --no-interlocking are SolverLN's layer-iteration "
6884 "knobs; a simulated sample path converges by run length, which is --samples");
6885 if (!kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
6886 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0)
6888 "--ln-transient* and --sens-* are SolverLN options; -s ldes has none of them");
6889 if (!kk.method.empty() && kk.method !=
"default")
6891 "--method on the layered path names the LN UPDATE (default, moment3, mwba.*); "
6892 "-s ldes performs no update, it simulates the model (got '" + kk.method +
"')");
6898 if (!kk.ldes_tranfilter.empty() || kk.ldes_warmupfrac >= 0.0 ||
6899 !kk.ldes_cimethod.empty() || kk.ldes_cnvgon || kk.ldes_cnvgtol > 0.0 ||
6900 kk.ldes_slotted || kk.ldes_slotlength > 0.0 || kk.ldes_replications > 0 ||
6901 kk.ldes_numthreads > 0 || kk.ldes_maxtime > 0.0 || !kk.ldes_initsol.empty() ||
6902 !kk.ldes_rest_url.empty())
6904 "the --ldes-* flags configure the SUBPROCESS engine that answers -s ldes on a "
6905 "Network (warmup filter, CI estimator, slot lattice, replications, warm-start "
6906 "placement); the native LN engine behind -i lqnx -s ldes reads --samples and "
6911 if (kk.has_cutoff())
6913 "--cutoff bounds the open population of a CTMC state space and applies to -s "
6914 "ctmc; the layered path enumerates no states");
6917 "--node selects the stateful node a CTMC query is labelled by; the layered path "
6918 "reports every LQN element");
6921 "--tspan sets the horizon of a transient analysis; the native LN engine runs to "
6922 "a completion budget, which is --samples");
6925 "--tol is not an LDES option; the run length is set with --samples");
6926 if (analysis ==
"cdf")
return run_ln_ldes_cdf(file, output, kk);
6927 return run_ln_ldes(file, output, kk);
6931 if (s ==
"ln.comom") {
6932 if (!kk.layer_solver.empty() && kk.layer_solver !=
"nc")
6933 throw line::InputError(
"-s ln.comom already selects NC layers, but --layer-solver says '" +
6934 kk.layer_solver +
"'");
6935 kk.layer_solver =
"nc";
6936 }
else if (s ==
"ln.mva") {
6937 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva")
6938 throw line::InputError(
"-s ln.mva already selects MVA layers, but --layer-solver says '" +
6939 kk.layer_solver +
"'");
6940 kk.layer_solver =
"mva";
6945 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva" && kk.layer_solver !=
"nc" &&
6946 kk.layer_solver !=
"fluid" && kk.layer_solver !=
"ssa")
6947 throw line::InputError(
"--layer-solver takes mva, nc, fluid or ssa (got '" +
6948 kk.layer_solver +
"')");
6950 if ((k.samples || k.seed) && kk.layer_solver !=
"ssa")
6952 "--samples and --seed set the run length and the stream of a SIMULATED layer; the "
6953 "layered path draws no random numbers unless --layer-solver ssa is in force");
6956 "--cutoff bounds the open population of a CTMC state space and applies to -s ctmc; the "
6957 "layered path enumerates no states");
6958 if (k.t1 >= 0.0 && analysis !=
"tran")
6960 "--tspan sets the horizon of a transient analysis and applies to the layered path "
6961 "only with -a tran");
6962 if (analysis ==
"tran" && !(k.t1 >= 0.0))
6964 "-a tran integrates each layer's drift and needs a horizon: pass --tspan <t0>:<t1>");
6967 "--node selects the stateful node a CTMC query is labelled by; the layered path "
6968 "reports every LQN element");
6971 "--tol is not a SolverLN option (LnOptions carries iter_tol and iter_max); "
6976 if (!k.method.empty() && k.method !=
"default" && k.method !=
"moment3" &&
6977 k.method !=
"mwba.upper" && k.method !=
"mwba.lower")
6979 "--method on the layered path takes default, moment3, mwba.upper or mwba.lower "
6980 "(got '" + k.method +
"'); the per-layer engine is chosen with --layer-solver");
6981 if ((k.method ==
"mwba.upper" || k.method ==
"mwba.lower") && analysis !=
"avg")
6983 "--method mwba.* reports a throughput and utilization BOUND and solves no layer, so "
6984 "it has no transient, no sensitivity and no response-time law; use -a avg");
6985 if (!k.sens_method.empty() && analysis !=
"sens")
6987 if (!k.ln_transient.empty() && analysis !=
"tran")
6990 if (analysis ==
"tran") {
6991 if (arith !=
"double")
6993 "the layered transient integrates each layer's drift with LSODA, which is double "
6994 "precision by construction; rerun with --arith double (got '" + arith +
"')");
6995 return run_ln_tran<double>(file, output, kk);
6997 if (analysis ==
"cdf") {
6998 if (arith !=
"double")
7000 "-a cdf fits an APH to a fluid passage time, integrated by LSODA in double "
7001 "precision; rerun with --arith double (got '" + arith +
"')");
7002 return run_ln_cdf<double>(file, output, kk);
7004 if (analysis ==
"sens") {
7005 if (arith ==
"double")
return run_ln_sens<double>(file, output, kk);
7006 if (arith ==
"exact")
return run_ln_sens<line::Rational>(file, output, kk);
7007 if (arith ==
"real:16")
return run_ln_sens<line::Real<16> >(file, output, kk);
7008 if (arith ==
"real" || arith ==
"real:32")
7009 return run_ln_sens<line::Real<32> >(file, output, kk);
7010 if (arith ==
"real:64")
return run_ln_sens<line::Real<64> >(file, output, kk);
7011 if (arith ==
"real:128")
return run_ln_sens<line::Real<128> >(file, output, kk);
7012 if (arith ==
"real:256")
return run_ln_sens<line::Real<256> >(file, output, kk);
7013 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7016 if (arith ==
"double")
return run_ln<double>(file, output, kk);
7017 if (arith ==
"exact")
return run_ln<line::Rational>(file, output, kk);
7019 if (arith ==
"real:16")
return run_ln<line::Real<16> >(file, output, kk);
7020 if (arith ==
"real" || arith ==
"real:32")
return run_ln<line::Real<32> >(file, output, kk);
7021 if (arith ==
"real:64")
return run_ln<line::Real<64> >(file, output, kk);
7022 if (arith ==
"real:128")
return run_ln<line::Real<128> >(file, output, kk);
7023 if (arith ==
"real:256")
return run_ln<line::Real<256> >(file, output, kk);
7024 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7072 const std::size_t M =
sn.nstations, K =
sn.nclasses;
7081 for (std::size_t j = 0; j < M; ++j)
7082 if (
sn.stations[j].name == a.
station_names[i]) { st_of[i] = j + 1;
break; }
7083 std::vector<std::size_t> cl_of(a.
class_names.size(), 0);
7084 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7085 for (std::size_t j = 0; j < K; ++j)
7086 if (
sn.classes[j].name == a.
class_names[c]) { cl_of[c] = j + 1;
break; }
7087 for (
int m = 0; m < 6; ++m) {
7090 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7091 if (st_of[i] && cl_of[c])
7092 (*dst[m])(st_of[i] - 1, cl_of[c] - 1) = ldes_at(*src[m], i, c);
7093 for (std::size_t f = 0; f < F; ++f)
7094 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7095 if (cl_of[c]) (*dst[m])(M + f, cl_of[c] - 1) = ldes_at(*fcr[m], f, c);
7098 for (std::size_t i = 0; i < M; ++i)
7099 for (std::size_t c = 0; c < K; ++c) RNs(i, c) = r.
RN(i, c);
7101 for (std::size_t i = 0; i < M; ++i)
7102 for (std::size_t c = 0; c < K; ++c) r.
WN(i, c) = WNs(i, c);
7103 for (std::size_t c = 0; c < K; ++c) {
7104 r.
CN.push_back(ldes_at(a.
CN, 0, c));
7105 r.
XN.push_back(ldes_at(a.
XN, 0, c));
7137 const std::string& s, std::string& banner,
7138 std::string* suffix =
nullptr,
7139 const std::string* file =
nullptr) {
7141 if (s ==
"jmt" || s ==
"ldes") {
7145 if constexpr (std::is_same_v<T, double>) {
7148 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
7149 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
7150 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
7156 banner =
"SolverJMT";
7161 std::snprintf(buf,
sizeof(buf),
" samples=%g seed=%ld", o.
samples, o.
seed);
7167 ldes_run(file ? *file : std::string(), o, std::vector<std::string>());
7168 r = avg_result_from_ldes(
sn, a);
7169 banner =
"SolverLDES";
7172 std::snprintf(buf,
sizeof(buf),
" engine=%s samples=%zu seed=%ld",
7180 }
else if (s ==
"ssa" || s ==
"fluid") {
7187 if constexpr (std::is_same_v<T, double>) {
7190 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
7191 if (k.samples)
opt.samples = k.samples;
7192 if (k.seed)
opt.seed = k.seed;
7197 std::vector<line::ssa::SsaCacheRatio>
cache;
7210 banner =
"SolverSSA";
7217 std::snprintf(buf,
sizeof(buf),
" samples=%zu seed=%lu time=%.6g", a.
samples,
7243 const bool has_ref = !refreshed.
nodes.empty();
7244 r = avg_result_from_sim<T>(has_ref ? refreshed :
sn, a.
QN, a.
UN, a.
RN, a.
TN, a.
CN,
7249 banner =
"SolverFluid";
7252 std::snprintf(buf,
sizeof(buf),
" iters=%zu", a.
iters);
7259 }
else if (s ==
"nc") {
7261 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
7262 if (k.tol >= 0.0)
opt.tol = k.tol;
7263 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
7264 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
7265 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
7267 banner =
"SolverNC";
7268 }
else if (s ==
"mam") {
7270 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
7271 if (k.tol >= 0.0)
opt.tol = k.tol;
7272 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
7274 banner =
"SolverMAM";
7275 }
else if (s ==
"ag") {
7277 apply_ag_knobs(k,
opt);
7279 banner =
"SolverAG";
7280 }
else if (s ==
"ba") {
7282 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
7284 banner =
"SolverBA";
7285 }
else if (s ==
"ctmc") {
7287 if (!k.method.empty())
opt.method = k.method;
7288 if (k.cutoff >= 0.0)
opt.cutoff = k.cutoff;
7289 opt.cutoff_mat = k.cutoff_mat;
7290 opt.force = k.force;
7293 banner =
"SolverCTMC";
7296 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
7297 if (k.tol >= 0.0)
opt.tol = k.tol;
7298 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
7299 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
7305 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
7306 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
7309 banner =
"SolverMVA";
7320int solve_model_node(
const std::string& file,
const Knobs& k,
const std::string& s) {
7323 std::string banner, suffix;
7326 const std::size_t I =
sn.nodes.size(), R =
sn.nclasses;
7327 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
7328 const line::Matrix<T>&QNn = nm.QN, &UNn = nm.UN, &RNn = nm.RN, &WNn = nm.WN, &ANn = nm.AN,
7343 const std::size_t F =
7344 r.
QN.rows() >
sn.nstations ? r.
QN.rows() -
sn.nstations :
static_cast<std::size_t
>(0);
7345 const double region_nan = std::numeric_limits<double>::quiet_NaN();
7346 auto region_name = [&](std::size_t f) {
7347 return (f <
sn.regions.size() && !
sn.regions[f].name.empty())
7348 ?
sn.regions[f].name
7349 :
"FCR" + std::to_string(f + 1);
7353 auto region_empty = [&](std::size_t f, std::size_t c) {
7354 return !(d(r.
QN(
sn.nstations + f, c)) > 0.0 || d(r.
RN(
sn.nstations + f, c)) > 0.0 ||
7355 d(r.
TN(
sn.nstations + f, c)) > 0.0);
7357 if (g_json_output) {
7363 p[
"type"] =
"AvgNodeTable";
7365 for (
const char* key : {
"Node",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
7367 p[key] = line::reg::Json::array();
7368 for (std::size_t i = 0; i < I; ++i)
7369 for (std::size_t c = 0; c < R; ++c) {
7372 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
7373 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
7375 p[
"Node"].push_back(
sn.nodes[i].name);
7376 p[
"JobClass"].push_back(
sn.classes[c].name);
7377 p[
"QLen"].push_back(d(QNn(i, c)));
7378 p[
"Util"].push_back(d(UNn(i, c)));
7379 p[
"RespT"].push_back(d(RNn(i, c)));
7380 p[
"ResidT"].push_back(d(WNn(i, c)));
7381 p[
"ArvR"].push_back(d(ANn(i, c)));
7382 p[
"Tput"].push_back(d(TNn(i, c)));
7384 for (std::size_t f = 0; f < F; ++f)
7385 for (std::size_t c = 0; c < R; ++c) {
7386 if (region_empty(f, c))
continue;
7387 p[
"Node"].push_back(region_name(f));
7388 p[
"JobClass"].push_back(
sn.classes[c].name);
7389 p[
"QLen"].push_back(d(r.
QN(
sn.nstations + f, c)));
7390 p[
"Util"].push_back(region_nan);
7391 p[
"RespT"].push_back(d(r.
RN(
sn.nstations + f, c)));
7392 p[
"ResidT"].push_back(d(r.
WN(
sn.nstations + f, c)));
7393 p[
"ArvR"].push_back(region_nan);
7394 p[
"Tput"].push_back(d(r.
TN(
sn.nstations + f, c)));
7401 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Node",
"JobClass",
"QLen",
"Util",
7402 "RespT",
"ResidT",
"ArvR",
"Tput");
7403 for (std::size_t i = 0; i < I; ++i)
7404 for (std::size_t c = 0; c < R; ++c) {
7405 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
7406 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
7408 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
7409 sn.nodes[i].name.c_str(),
sn.classes[c].name.c_str(), d(QNn(i, c)),
7410 d(UNn(i, c)), d(RNn(i, c)), d(WNn(i, c)), d(ANn(i, c)), d(TNn(i, c)));
7412 for (std::size_t f = 0; f < F; ++f)
7413 for (std::size_t c = 0; c < R; ++c) {
7414 if (region_empty(f, c))
continue;
7415 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
7416 region_name(f).c_str(),
sn.classes[c].name.c_str(),
7417 d(r.
QN(
sn.nstations + f, c)), region_nan, d(r.
RN(
sn.nstations + f, c)),
7418 d(r.
WN(
sn.nstations + f, c)), region_nan, d(r.
TN(
sn.nstations + f, c)));
7431double cache_at(
const std::vector<T>& v, std::size_t r) {
7432 if (r >= v.size())
return std::numeric_limits<double>::quiet_NaN();
7456int solve_model_cache(
const std::string& file,
const Knobs& k,
const std::string& s) {
7459 std::string banner, suffix;
7461 if (r.
cache.empty())
7463 "-a cache reports the per-Cache hit and miss table and this model has no Cache node, "
7464 "or the solver that ran analyzes none; SolverNC's cache branches are what fill it");
7469 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
7470 std::size_t srcnode = 0;
7471 for (std::size_t i = 0; i <
sn.nodes.size(); ++i)
7475 std::string node, cls;
7476 double list, listcap, items, hitp, dhitp, missp, hitr, dhitr, missr, arvr, residt, cost;
7478 std::vector<Row> rows;
7479 const double dnan = std::numeric_limits<double>::quiet_NaN();
7481 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
7483 const typename std::map<std::size_t, line::qn::CacheParam<T> >::const_iterator it =
7484 sn.nodeparam.find(m.
node);
7485 if (it ==
sn.nodeparam.end())
continue;
7486 const std::vector<std::size_t>& hitclass = it->second.hitclass;
7487 const std::size_t h = m.
itemcap.size();
7488 double totcap = 0.0;
7489 for (std::size_t l = 0; l < h; ++l) totcap += m.
itemcap[l];
7490 double totcost = dnan;
7493 for (std::size_t l = 0; l < m.
listcost.size(); ++l)
7497 for (std::size_t cl = 0; cl <
sn.nclasses; ++cl) {
7498 if (cl >= hitclass.size() || hitclass[cl] == 0)
continue;
7501 if (std::isnan(ph) && std::isnan(pm) && std::isnan(pd))
continue;
7502 if (std::isnan(ph)) ph = 0.0;
7503 if (std::isnan(pm)) pm = 0.0;
7504 if (std::isnan(pd)) pd = 0.0;
7507 const double lat = cache_at(m.
latency, cl);
7510 t.node =
sn.nodes[m.
node - 1].name;
7511 t.cls =
sn.classes[cl].name;
7514 t.items =
static_cast<double>(m.
nitems);
7519 t.dhitr = arvr * pd;
7520 t.missr = arvr * pm;
7521 t.arvr = arvr * (pm + pd);
7529 for (std::size_t l = 0; l < m.
hitproblist.cols(); ++l)
7533 for (std::size_t l = 0; l < h; ++l) {
7537 if (std::isnan(phl)) phl = 0.0;
7541 u.list =
static_cast<double>(l + 1);
7547 u.hitr = arvr * phl;
7560 if (g_json_output) {
7562 p[
"type"] =
"AvgCacheTable";
7564 for (
const char* key : {
"Node",
"JobClass",
"List",
"ListCap",
"Items",
"HitProb",
7565 "DelayedHitProb",
"MissProb",
"HitRate",
"DelayedHitRate",
7566 "MissRate",
"ArvR",
"ResidT",
"ListCost"})
7567 p[key] = line::reg::Json::array();
7568 for (std::size_t i = 0; i < rows.size(); ++i) {
7569 p[
"Node"].push_back(rows[i].node);
7570 p[
"JobClass"].push_back(rows[i].cls);
7571 p[
"List"].push_back(rows[i].list);
7572 p[
"ListCap"].push_back(rows[i].listcap);
7573 p[
"Items"].push_back(rows[i].items);
7574 p[
"HitProb"].push_back(rows[i].hitp);
7575 p[
"DelayedHitProb"].push_back(rows[i].dhitp);
7576 p[
"MissProb"].push_back(rows[i].missp);
7577 p[
"HitRate"].push_back(rows[i].hitr);
7578 p[
"DelayedHitRate"].push_back(rows[i].dhitr);
7579 p[
"MissRate"].push_back(rows[i].missr);
7580 p[
"ArvR"].push_back(rows[i].arvr);
7581 p[
"ResidT"].push_back(rows[i].residt);
7582 p[
"ListCost"].push_back(rows[i].cost);
7589 std::printf(
"%-14s %-12s %5s %8s %6s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
"Node",
7590 "JobClass",
"List",
"ListCap",
"Items",
"HitProb",
"DHitProb",
"MissProb",
7591 "HitRate",
"DHitRate",
"MissRate",
"ArvR",
"ResidT",
"ListCost");
7592 for (std::size_t i = 0; i < rows.size(); ++i)
7594 "%-14s %-12s %5g %8g %6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g "
7596 rows[i].node.c_str(), rows[i].cls.c_str(), rows[i].list, rows[i].listcap,
7597 rows[i].items, rows[i].hitp, rows[i].dhitp, rows[i].missp, rows[i].hitr,
7598 rows[i].dhitr, rows[i].missr, rows[i].arvr, rows[i].residt, rows[i].cost);
7616int solve_model_item(
const std::string& file,
const Knobs& k,
const std::string& s) {
7619 std::string banner, suffix;
7621 if (r.
cache.empty())
7623 "-a item reports the per-item cache occupancy and this model has no Cache node, or "
7624 "the solver that ran analyzes none");
7626 const double dnan = std::numeric_limits<double>::quiet_NaN();
7629 double item, list, listcap, size, prob, cost, dhq, dhqf;
7631 std::vector<Row> rows;
7632 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
7634 const std::size_t h = m.
itemcap.size();
7640 const std::size_t nit =
7642 for (std::size_t i = 0; i < nit; ++i)
7643 for (std::size_t l = 0; l < h; ++l) {
7645 t.node =
sn.nodes[m.
node - 1].name;
7646 t.item =
static_cast<double>(i + 1);
7647 t.list =
static_cast<double>(l + 1);
7652 t.prob = (l + 1) < m.
itemprob.cols()
7655 t.cost = t.size * t.prob;
7667 "-a item needs a per-item occupancy law and this solve produced none; the NC/MVA "
7668 "cache recursions (isolated and integrated alike) and the delayed-hit retrieval "
7669 "algorithms compute the embedded one, SolverCTMC the time-weighted one, and the "
7672 if (g_json_output) {
7674 p[
"type"] =
"AvgItemTable";
7676 for (
const char* key : {
"Node",
"Item",
"List",
"ListCap",
"Size",
"Prob",
"Cost",
7677 "DelayedHitQLen",
"DelayedHitQLenFull"})
7678 p[key] = line::reg::Json::array();
7679 for (std::size_t i = 0; i < rows.size(); ++i) {
7680 p[
"Node"].push_back(rows[i].node);
7681 p[
"Item"].push_back(rows[i].item);
7682 p[
"List"].push_back(rows[i].list);
7683 p[
"ListCap"].push_back(rows[i].listcap);
7684 p[
"Size"].push_back(rows[i].size);
7685 p[
"Prob"].push_back(rows[i].prob);
7686 p[
"Cost"].push_back(rows[i].cost);
7687 p[
"DelayedHitQLen"].push_back(rows[i].dhq);
7688 p[
"DelayedHitQLenFull"].push_back(rows[i].dhqf);
7695 std::printf(
"%-14s %6s %6s %8s %10s %12s %12s %14s %18s\n",
"Node",
"Item",
"List",
"ListCap",
7696 "Size",
"Prob",
"Cost",
"DelayedHitQLen",
"DelayedHitQLenFull");
7697 for (std::size_t i = 0; i < rows.size(); ++i)
7698 std::printf(
"%-14s %6g %6g %8g %10g %12.8g %12.8g %14.8g %18.8g\n", rows[i].node.c_str(),
7699 rows[i].item, rows[i].list, rows[i].listcap, rows[i].size, rows[i].prob,
7700 rows[i].cost, rows[i].dhq, rows[i].dhqf);
7713int solve_model_sys(
const std::string& file,
const Knobs& k,
const std::string& s) {
7716 std::string banner, suffix;
7723 if (g_json_output) {
7729 p[
"type"] =
"AvgSysTable";
7731 for (
const char* key : {
"Chain",
"JobClasses",
"SysRespT",
"SysTput"})
7732 p[key] = line::reg::Json::array();
7733 for (std::size_t c = 0; c <
sn.nchains; ++c) {
7734 p[
"Chain"].push_back(cn[c]);
7735 p[
"JobClasses"].push_back(cc[c]);
7736 p[
"SysRespT"].push_back(d(sys.
CN[c]));
7737 p[
"SysTput"].push_back(d(sys.
XN[c]));
7742 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner.c_str(),
7744 std::printf(
"%-10s %-24s %14s %14s\n",
"Chain",
"JobClasses",
"SysRespT",
"SysTput");
7745 for (std::size_t c = 0; c <
sn.nchains; ++c)
7746 std::printf(
"%-10s %-24s %14.6g %14.6g\n", cn[c].c_str(), cc[c].c_str(), d(sys.
CN[c]),
7753void print_chain_table(
const char* key,
const char* type,
const char* rowlabel,
7754 const std::vector<std::string>& rows,
7755 const std::vector<std::string>& chains,
7756 const std::vector<std::string>& classes,
7758 const char* banner,
const char* arith,
const char* suffix =
"") {
7760 if (g_json_output) {
7766 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
7767 chains.size(), suffix);
7771 for (
const char* c : {rowlabel,
"Chain",
"JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
7773 p[c] = line::reg::Json::array();
7777 for (std::size_t i = 0; i < rows.size(); ++i)
7778 for (std::size_t c = 0; c < chains.size(); ++c) {
7779 p[rowlabel].push_back(rows[i]);
7780 p[
"Chain"].push_back(chains[c]);
7781 p[
"JobClasses"].push_back(classes[c]);
7782 p[
"QLen"].push_back(d(t.
QN(i, c)));
7783 p[
"Util"].push_back(d(t.
UN(i, c)));
7784 p[
"RespT"].push_back(d(t.
RN(i, c)));
7785 p[
"ResidT"].push_back(d(t.
WN(i, c)));
7786 p[
"ArvR"].push_back(d(t.
AN(i, c)));
7787 p[
"Tput"].push_back(d(t.
TN(i, c)));
7789 emit_analysis<T>(key, p, method);
7792 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
7793 chains.size(), suffix);
7794 std::printf(
"%-16s %-10s %-20s %12s %12s %12s %12s %12s %12s\n", rowlabel,
"Chain",
7795 "JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
7796 for (std::size_t i = 0; i < rows.size(); ++i)
7797 for (std::size_t c = 0; c < chains.size(); ++c)
7798 std::printf(
"%-16s %-10s %-20s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
7799 rows[i].c_str(), chains[c].c_str(), classes[c].c_str(), d(t.
QN(i, c)),
7800 d(t.
UN(i, c)), d(t.
RN(i, c)), d(t.
WN(i, c)), d(t.
AN(i, c)),
7814int solve_model_chain(
const std::string& file,
const Knobs& k,
const std::string& s) {
7817 std::string banner, suffix;
7820 std::vector<std::string> rows;
7821 for (std::size_t i = 0; i <
sn.nstations; ++i) rows.push_back(
sn.stations[i].name);
7822 print_chain_table<T>(
"chain",
"AvgChainTable",
"Station", rows,
7831int solve_model_nodechain(
const std::string& file,
const Knobs& k,
const std::string& s) {
7834 std::string banner, suffix;
7836 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
7839 std::vector<std::string> rows;
7840 for (std::size_t i = 0; i <
sn.nodes.size(); ++i) rows.push_back(
sn.nodes[i].name);
7841 print_chain_table<T>(
"nodechain",
"AvgNodeChainTable",
"Node", rows,
7849inline bool is_avg_view(
const std::string& analysis) {
7850 return analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
7851 analysis ==
"nodechain";
7864int solve_avg_view(
const std::string& file,
const Knobs& k,
const std::string& s,
7865 const std::string& analysis) {
7866 if (analysis ==
"node")
return solve_model_node<T>(file, k, s);
7867 if (analysis ==
"sys")
return solve_model_sys<T>(file, k, s);
7868 if (analysis ==
"chain")
return solve_model_chain<T>(file, k, s);
7869 return solve_model_nodechain<T>(file, k, s);
7872int solve_model_dispatch(
const std::string& arith,
const std::string& solver,
7873 const std::string& analysis,
const std::string& file,
const Knobs& k) {
7874 std::string s = solver.empty() ?
"auto" : solver;
7875 if (s !=
"mva" && s !=
"auto" && s !=
"fluid" && s !=
"fld" && s !=
"nc" && s !=
"mam" &&
7876 s !=
"ag" && s !=
"ba" && s !=
"ssa" && s !=
"ctmc" && s !=
"uq" && s !=
"env" &&
7877 s !=
"qns" && s !=
"ldes" && s !=
"jmt")
7879 "the model-solving path ports -s mva, nc, ctmc, mam, ag, ba, ssa, fluid, ldes, jmt, "
7880 "uq, env and qns (got '" + s +
"'); other solvers remain API-only");
7887 if ((k.verbose && s !=
"ldes") || k.remote || !k.remote_url.empty() ||
7888 (k.keep && s !=
"qns" && s !=
"jmt"))
7890 "--keep, --verbose, --remote and --remote-url describe the child process of an "
7891 "external solver; on this path -s jmt and -s qns run one and take --keep, -s ldes "
7892 "runs one and takes --verbose (which echoes the resolved engine command line), and "
7893 "no path takes --remote or --remote-url");
7894 if (k.timeout_seconds && s !=
"qns")
7896 "--timeout is the deadline of an external solver's child process; on this path only "
7903 if (!k.fork_join.empty() && s !=
"mva" && s !=
"nc")
7905 "--fork-join selects the fork-join transform of the shared mean-value fixed point "
7906 "and is read by -s mva and -s nc; -s " + s +
7907 " either simulates or enumerates the fork and applies no transform");
7912 if (k.warmupfrac >= 0.0 && s !=
"ssa")
7914 "--warmupfrac discards a leading fraction of a SIMULATED path before the means are "
7915 "taken and is read by -s ssa; -s " + s +
7916 " has no path to discard (the LDES engine takes --ldes-warmupfrac)");
7917 if (k.pstar > 0.0 && s !=
"fluid" && s !=
"fld")
7919 "--pstar is the exponent of the fluid p-norm smoothing of the drift and is read by "
7920 "-s fluid; -s " + s +
" integrates no drift");
7921 if ((!k.busy_orders.empty() || !k.busy_subnet.empty()) && analysis !=
"busyperiod")
7923 "--busyperiod and --busyperiod-subnet name the orders and the subnetwork of "
7924 "-a busyperiod; got -a " + analysis);
7938 if (analysis !=
"avg" && analysis !=
"posterior" && analysis !=
"interval")
7940 "SolverUQ ports -a avg (the prior-weighted expectation), -a posterior (the "
7941 "per-design-point table) and -a interval (the support-only range); got '" +
7943 if (k.uq_solver.empty())
7945 "-s uq needs --uq-solver: UQ computes nothing itself, it expands the Prior and "
7946 "runs another solver at each design point (the C++ spelling of UQ(model, "
7947 "@SolverMVA)). Naming one here by default would attribute the numbers to an "
7948 "engine the caller never chose");
7949 if (k.t1 >= 0.0 || k.node || !k.notation.empty())
7951 "--tspan, --node and --notation name a transient horizon, a stateful node and an "
7952 "ODE document; SolverUQ reports steady-state means over a design of models and "
7953 "has none of the three");
7954 if (k.no_interlocking || k.repeat > 0 || !k.layer_solver.empty())
7956 "--no-interlocking, --repeat and --layer-solver are options of the layered solver "
7957 "and apply to -i lqnx; a Network model has no layers to interlock");
7958 if (arith ==
"double")
return solve_model_uq<double>(file, k, analysis);
7959 if (arith ==
"exact")
return solve_model_uq<line::Rational>(file, k, analysis);
7960 if (arith ==
"real:16")
return solve_model_uq<line::Real<16> >(file, k, analysis);
7961 if (arith ==
"real" || arith ==
"real:32")
7962 return solve_model_uq<line::Real<32> >(file, k, analysis);
7963 if (arith ==
"real:64")
return solve_model_uq<line::Real<64> >(file, k, analysis);
7964 if (arith ==
"real:128")
return solve_model_uq<line::Real<128> >(file, k, analysis);
7965 if (arith ==
"real:256")
return solve_model_uq<line::Real<256> >(file, k, analysis);
7966 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7975 if (analysis !=
"avg")
7977 "SolverENV reports -a avg, the environment-blended means; getEnsembleAvg is its "
7978 "only metric entry in the reference too (got '" + analysis +
"')");
7979 if (k.samples || k.seed)
7981 "--samples and --seed describe a simulation; SolverENV iterates a fixed point over "
7982 "transient stage solves and draws nothing");
7987 if (k.has_cutoff() && k.stage_solver !=
"ctmc")
7989 "--cutoff bounds the open population of an enumerated state space and applies to "
7990 "-s env only beside --stage-solver ctmc; the fluid stages of this ensemble "
7991 "enumerate no states");
7992 if (k.node || !k.notation.empty())
7994 "--node and --notation name a stateful node and an ODE document of ONE network; "
7995 "an Environment holds a network per stage and "
7996 "SolverENV reports the blend over them");
7997 if (k.no_interlocking || k.repeat > 0 || !k.layer_solver.empty())
7999 "--no-interlocking, --repeat and --layer-solver are options of the layered solver "
8000 "and apply to -i lqnx; an Environment has stages, not layers");
8003 "--tspan on the ENV path states the transient HORIZON each stage solve integrates "
8004 "to, and every stage starts from its entry state at 0; a nonzero t0 would name a "
8005 "start the coupling has no state for");
8010 const std::string coupling =
8011 (k.method.empty() || k.method ==
"default") ?
"meanfield" : k.method;
8012 if (k.tran_points && (coupling ==
"statevec" || coupling ==
"blend"))
8014 "--tran-points is the mean-field coupling's quadrature grid; the state-vector "
8015 "coupling carries the whole joint law across a switch and sums over no such grid, "
8016 "so the value would be accepted and never used");
8023 if (coupling ==
"statedep")
8025 "--method statedep makes each environment transition depend on the state its "
8026 "stage is left in, through a rate function per arc that no model.json can carry "
8027 "(the reference cannot serialize resetEnvRatesFun either); it is reachable from "
8028 "the C++ API, through Environment::set_env_rate_reset");
8029 if ((k.tran_points || k.t1 >= 0.0) && (coupling ==
"avg" || coupling ==
"dec"))
8031 "--tran-points and --tspan state the grid and the horizon of a TRANSIENT stage "
8032 "solve; the closed-form limits --method avg and --method dec solve in steady "
8033 "state and carry nothing across a switch, so both would be accepted and never "
8035 if (arith !=
"double" && coupling !=
"statevec" && coupling !=
"blend")
8037 "SolverENV solves a stage with the fluid analyzer on every method but the "
8038 "state-vector one -- the mean-field coupling transiently, the avg and dec limits "
8039 "in steady state -- and that analyzer is LSODA's and therefore double; --arith " +
8041 " reaches ENV only through the state-vector coupling (--method statevec or --method blend)");
8042 if (arith ==
"double")
return solve_model_env<double>(file, k);
8043 if (arith ==
"exact")
return solve_model_env<line::Rational>(file, k);
8044 if (arith ==
"real:16")
return solve_model_env<line::Real<16> >(file, k);
8045 if (arith ==
"real" || arith ==
"real:32")
8046 return solve_model_env<line::Real<32> >(file, k);
8047 if (arith ==
"real:64")
return solve_model_env<line::Real<64> >(file, k);
8048 if (arith ==
"real:128")
return solve_model_env<line::Real<128> >(file, k);
8049 if (arith ==
"real:256")
return solve_model_env<line::Real<256> >(file, k);
8050 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8052 if (!k.uq_solver.empty())
8054 "--uq-solver names the engine SolverUQ runs at each design point and applies to -s uq; "
8055 "'" + s +
"' solves one model, not a design of them");
8058 "--tran-points is the resolution of the transient grid SolverENV sums its stage exit "
8059 "metrics over and applies to -s env; '" + s +
"' has no such quadrature");
8065 const bool ldes_knob = !k.ldes_tranfilter.empty() || k.ldes_warmupfrac >= 0.0 ||
8066 !k.ldes_cimethod.empty() || k.ldes_cnvgon || k.ldes_slotted ||
8067 k.ldes_replications > 0 || k.ldes_numthreads > 0 ||
8068 k.ldes_maxtime > 0.0 || !k.ldes_initsol.empty() ||
8069 !k.ldes_rest_url.empty();
8070 if (ldes_knob && s !=
"ldes" && s !=
"auto")
8072 "the --ldes-* flags are the discrete-event engine's own settings (warmup filter, "
8073 "confidence-interval estimator, slot lattice, replications, warm-start placement) and "
8074 "apply to -s ldes; '" + s +
"' has none of them");
8076 if (arith !=
"double")
8078 "SolverJMT is a client of the Java Modelling Tools engine, which simulates in "
8079 "double and reports in double; --arith " + arith +
8080 " would label a double answer with an arithmetic that never touched it");
8081 if (analysis !=
"avg" && analysis !=
"cdf" && analysis !=
"trancdf" &&
8082 analysis !=
"trancdfpasst" && analysis !=
"prob" && !is_avg_view(analysis))
8084 "-s jmt reports -a avg (the JSIM or JMVA mean table), its four views -a node, "
8085 "-a sys, -a chain and -a nodechain, -a cdf (the empirical response-time law read "
8086 "back from the JMT logs, preloaded at the rounded steady-state queue lengths), "
8087 "-a tran-cdf-respt and -a tran-cdf-passt (the same logged run from the default "
8088 "initial state, so the samples cover the transient) and -a prob (the time each "
8089 "declared state is held for along the logged trajectory); -a tranprob needs one "
8090 "run per replication and is not exposed here");
8092 if (!k.method.empty() &&
8093 std::find(valid.begin(), valid.end(), k.method) == valid.end())
8095 "SolverJMT methods are default, jsim and the jmva family (jmva, jmva.amva, "
8096 "jmva.mva, jmva.recal, jmva.comom, jmva.chow, jmva.bs, jmva.aql, jmva.lin, "
8097 "jmva.dmlin); got '" + k.method +
"'");
8098 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0 || !k.multiserver.empty())
8100 "--tol, --iter_tol, --iter_max and --multiserver configure a fixed-point "
8101 "iteration; JSIM simulates a sample path and JMVA takes its tolerance from the "
8102 "exported document. The simulation's stopping rule is --samples");
8105 "--cutoff truncates an enumerated state space; JMT enumerates none");
8111 if ((k.node && analysis !=
"prob") || k.jobclass || !k.marg_states.empty())
8113 "--node, --class and --marg-states select the marginal law of one (node, class); "
8114 "the JMT arms report tables over every station and class, and -a prob takes "
8115 "--node only to say which station --state overrides");
8116 if (!k.state.empty() && analysis !=
"prob")
8118 "--state names the state a probability is asked about and applies to -a prob");
8119 if (!k.state.empty() && !k.node)
8121 "--state is the per-class job count of ONE station and needs --node to say "
8122 "which; a bare count vector cannot be matched against a whole network");
8123 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
8125 "--notation, --symbolic and --equilibria describe an exported ODE document; JMT "
8126 "integrates no ODE");
8127 if (!k.cdf_algorithm.empty())
8129 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
8130 "JMT response-time law is the ecdf of the passages its loggers recorded and is "
8131 "not computed from a transform");
8132 if (is_avg_view(analysis))
return solve_avg_view<double>(file, k,
"jmt", analysis);
8133 return solve_model_jmt(file, k, analysis);
8136 if (arith !=
"double")
8138 "SolverLDES is a client of the SSJ engine, which simulates in double and reports "
8139 "in double; --arith " + arith +
8140 " would label a double answer with an arithmetic that never touched it");
8147 if (!k.method.empty() && k.method !=
"default" && k.method !=
"parallel")
8149 "SolverLDES has two methods, 'default' and 'parallel' (listValidMethods returns "
8150 "exactly those in every codebase); got '" + k.method +
"'");
8152 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0 || !k.multiserver.empty())
8154 "--tol, --iter_tol, --iter_max and --multiserver are the knobs of a fixed-point "
8155 "iteration; LDES simulates a sample path and iterates nothing. Its stopping rule "
8156 "is --samples, or --ldes-cnvgon with --ldes-cnvgtol");
8159 "--cutoff truncates an enumerated state space; a simulator visits the states the "
8160 "sample path reaches and enumerates none");
8161 if (k.node || k.jobclass || !k.marg_states.empty())
8163 "--node, --class and --marg-states select a marginal law of one (node, class); the "
8164 "LDES arms report tables over every station and class, and -a prob is refused for "
8165 "want of a target state");
8166 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
8168 "--notation, --symbolic and --equilibria describe an exported ODE document; LDES "
8169 "integrates no ODE");
8170 if (!k.cdf_algorithm.empty())
8172 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
8173 "LDES response-time law is the ecdf of the samples the engine recorded and is not "
8174 "computed from a transform");
8175 if (k.no_interlocking || k.repeat > 0 || !k.layer_solver.empty())
8177 "--no-interlocking, --repeat and --layer-solver are options of the layered solver "
8178 "and apply to -i lqnx; the LDES layered path runs in the JAR's own ensemble "
8179 "backend and has no JSON interface to reach from here");
8180 if (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step >= 0.0)
8182 "--sens-method, --sens-scheme and --sens-step configure the layered sensitivity "
8183 "table; LDES reports no sensitivity");
8184 if (analysis ==
"tran" && !(k.t1 >= 0.0))
8186 "-s ldes -a tran needs --tspan <t1> or --tspan <t0>:<t1>: a trajectory over an unstated "
8187 "horizon is not a quantity, and the engine only records buckets once a timespan "
8188 "makes the run transient");
8189 if (k.t1 >= 0.0 && analysis !=
"tran")
8191 "--tspan names the horizon of -a tran; -a sample runs over [0, --samples], which "
8192 "is the horizon runTransientJson uses, and the other arms are steady state");
8199 if ((analysis ==
"trancdf" || analysis ==
"trancdfpasst") &&
8200 k.verbosity !=
"silent")
8201 std::fprintf(stderr,
8202 "Warning: -a %s is the ecdf of the per-job response times the run "
8203 "observed, the same curve -a cdf reports; the reference's LDES "
8204 "getTranCdfRespT reads the same samples\n",
8209 if (kldes.method ==
"parallel" && kldes.ldes_replications <= 1) kldes.ldes_replications = 8;
8210 if (is_avg_view(analysis))
return solve_avg_view<double>(file, kldes,
"ldes", analysis);
8211 return solve_model_ldes(file, kldes, analysis);
8221 if (analysis ==
"aoi")
8223 "-a aoi is the AoI branch of the fluid 'mfq' method and no other engine reports "
8224 "it, so SolverAUTO does not choose for it: ask for it by name with -s fluid");
8225 const AutoPlan plan = choose_auto_plan_dispatch(arith, file, analysis, k.method);
8232 if (plan.order.size() == 1) {
8237 kk.method = plan.method;
8238 std::printf(
"SolverAUTO selected %s%s\n", plan.order[0].c_str(), plan.note.c_str());
8239 return solve_model_dispatch(arith, plan.order[0], analysis, file, kk);
8241 std::string first_error;
8242 for (std::size_t i = 0; i < plan.order.size(); ++i) {
8244 kk.method = (i == 0) ? plan.method : std::string();
8246 std::printf(
"SolverAUTO selected %s%s\n", plan.order[i].c_str(),
8249 std::printf(
"SolverAUTO retrying with %s\n", plan.order[i].c_str());
8251 return solve_model_dispatch(arith, plan.order[i], analysis, file, kk);
8253 if (first_error.empty()) first_error = plan.order[i] +
": " + e.what();
8254 std::printf(
"SolverAUTO: %s cannot serve this run (%s)\n", plan.order[i].c_str(),
8259 "SolverAUTO: every candidate refused this run. The chosen engine reported -- " +
8266 const bool is_sim = (s ==
"ssa");
8273 const bool is_cftp =
8274 (s ==
"ctmc" && (k.method ==
"cftp" || k.method ==
"cftp.approx"));
8275 const bool draws_samples = is_sim || (s ==
"ctmc" && analysis ==
"sample") || is_cftp;
8276 if (k.samples && !draws_samples)
8278 "-s ctmc -a sample and to -s ctmc --method cftp; '" + s +
8279 "' has no sample count");
8280 if (k.seed && !draws_samples)
8282 "-s ctmc -a sample and to -s ctmc --method cftp; '" + s +
8283 "' draws no random numbers");
8287 if ((k.mdd_tol > 0.0 || k.mdd_maxiter > 0) && !(s ==
"ctmc" && k.method ==
"mdd"))
8289 "--mdd-tol and --mdd-maxiter set the coupled level iteration of -s ctmc --method mdd; "
8290 "'" + s +
" / " + (k.method.empty() ? std::string(
"default") : k.method) +
8291 "' iterates no levels");
8300 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")) &&
8301 !(s ==
"fluid" || s ==
"fld") && !(s ==
"mam" && analysis ==
"tran"))
8303 "--tspan sets the horizon of a transient analysis and applies to -s ctmc -a tran, "
8304 "-a tranprob and -a tranreward, to -s mam -a tran and to -s fluid; '" + s +
" / " +
8305 analysis +
"' integrates no forward equation");
8313 if (k.node && !(s ==
"ctmc" && (analysis ==
"tranprob" || analysis ==
"sample")) &&
8314 !(s ==
"ctmc" && analysis ==
"prob" && !k.state.empty()) &&
8315 !(s ==
"nc" && analysis ==
"prob" && !k.state.empty()) &&
8316 !(s ==
"ssa" && analysis ==
"sample") && !(s ==
"mam" && analysis ==
"prob") &&
8317 !((s ==
"mva" || s ==
"nc") && analysis ==
"marg"))
8319 "--node selects the stateful node a state query is labelled by and applies to -s ctmc "
8320 "-a tranprob and -a sample, to -s ctmc|nc -a prob beside --state, to -s ssa -a sample, "
8321 "to -s mam -a prob and to -s mva|nc -a marg; '" + s +
" / " + analysis +
8322 "' reports the whole network");
8326 if ((k.jobclass || !k.marg_states.empty()) && !(s ==
"mva" && analysis ==
"marg"))
8328 "--class and --marg-states are the job class and the state list of getProbMarg and "
8329 "apply to -s mva -a marg; '" + s +
" / " + analysis +
8330 "' reports every class over its own range");
8334 if (!k.notation.empty() && !((s ==
"fluid" || s ==
"fld") && analysis ==
"odes"))
8336 "--notation selects the form of the exported ODE document and applies to -s fluid -a "
8337 "odes; '" + s +
" / " + analysis +
"' exports no equations");
8340 if ((!k.symbolic.empty() || k.equilibria) &&
8341 !((s ==
"fluid" || s ==
"fld") && analysis ==
"jacobian"))
8343 "--symbolic selects the computer-algebra backend and --equilibria asks it for the "
8344 "solutions of f(x) = 0; both apply to -s fluid -a jacobian, and '" + s +
" / " +
8345 analysis +
"' consults no backend");
8354 if (!k.state.empty() &&
8355 !(analysis ==
"prob" && (s ==
"ctmc" || s ==
"auto" || s ==
"jmt" || s ==
"nc")))
8357 "--state names the state `getProb(node, state)` asks about and applies to -a prob "
8358 "under -s ctmc, -s nc and -s jmt, the arms whose answer is indexed by a state; '" + s +
8359 " / " + analysis +
"' reports a mean or a law over all of them");
8360 if (k.events && analysis !=
"sample")
8362 "--events is the length of ONE sampled trajectory and applies to -a sample; use "
8363 "--samples for a solver's run length ('" + s +
" / " + analysis +
"')");
8364 if (!k.percentiles.empty() && !(s ==
"mam" && (analysis ==
"cdf" || analysis ==
"cdfpasst" ||
8365 analysis ==
"perct")))
8367 "--percentiles names the levels getPerctRespT is read at and applies to -s mam -a "
8368 "perct-respt (and to the percentiles printed beside -a cdf); '" + s +
" / " +
8369 analysis +
"' inverts no response-time law");
8370 if (!k.reward_name.empty() && analysis !=
"rewardvalue")
8372 "--reward-name selects which declared reward -a reward-value returns the value "
8373 "function of; -a reward returns every reward's steady-state expectation and needs no "
8374 "name ('" + s +
" / " + analysis +
"')");
8375 if (k.timestep > 0.0 &&
8377 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
8379 "--timestep is the fixed output grid of a transient CTMC solve, `options.timestep` of "
8380 "ctmc_transient.m, and applies to -s ctmc -a tran, -a tranprob and -a tranreward; the "
8382 "and simulated transients report the points their own integrator or engine produced "
8383 "('" + s +
" / " + analysis +
"')");
8384 if ((!k.transient_method.empty() || k.fau_epsilon > 0.0 || k.fau_delta >= 0.0) &&
8386 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
8388 "--transient-method (and --fau-epsilon / --fau-delta) selects how the CTMC forward "
8389 "equation is advanced, `options.config.transient_method` of "
8390 "solver_ctmc_transient_analyzer.m, and applies to -s ctmc -a tran, -a tranprob and "
8391 "-a tranreward; every other analysis solves no forward equation ('" +
8392 s +
" / " + analysis +
"')");
8396 if (!k.cdf_algorithm.empty() && !(s ==
"nc" && analysis ==
"cdf"))
8398 "--cdf-algorithm selects the sojourn-time inversion of the NC response-time "
8399 "distribution and applies to -s nc -a cdf; '" + s +
" / " + analysis +
8400 "' inverts no generating function");
8405 const bool passage_arm =
8406 (s ==
"ctmc" && (analysis ==
"firstpasst" || analysis ==
"firstpasstmom"));
8407 if ((!k.passage_from.empty() || !k.passage_into.empty() || k.passage_orders > 0) &&
8410 "--passage-from, --passage-into and --passage-orders name the state sets and the "
8411 "moment order of -s ctmc -a firstpasst / firstpasstmom; '" + s +
" / " + analysis +
8412 "' times no state-set passage");
8413 if (!k.passage_method.empty() && !(s ==
"ctmc" && analysis ==
"firstpasst"))
8415 "--passage-method selects the transform inversion of -s ctmc -a firstpasst; '" + s +
8416 " / " + analysis +
"' inverts none (the moments arm solves for them directly)");
8419 if (k.method_perm !=
"exact" && !(s ==
"nc" && analysis ==
"sysmarg"))
8421 "--perm-engine selects the permanent estimator of the NC joint total-queue-length "
8422 "law and applies to -s nc -a sysmarg; '" + s +
" / " + analysis +
8423 "' evaluates no permanent");
8429 const bool nc_sens = s ==
"nc" && analysis ==
"sens";
8430 if (!nc_sens && (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step > 0.0))
8432 "--sens-method, --sens-scheme and --sens-step select the branch of a sensitivity "
8433 "table and apply to -i lqnx -a sens or to -s nc -a sens; '" + s +
" / " + analysis +
8434 "' differentiates nothing");
8435 if (k.no_interlocking || k.repeat > 0 || !k.layer_solver.empty() ||
8436 !k.ln_transient.empty() || !k.ln_transient_channels.empty())
8438 "--no-interlocking, --repeat, --layer-solver and --ln-transient* are options "
8439 "of the layered solver and apply to -i lqnx; a Network model has no layers to "
8441 if (s ==
"ba" && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
8443 "--tol, --iter_tol and --iter_max do not apply to -s ba: a bound is a closed form, "
8444 "with nothing to converge");
8448 if (s !=
"ba" && (!k.qrf_params.empty() || !k.qrf_alpha.empty()))
8450 "--qrf-params and --qrf-alpha parameterise the QRF reduction bounds and apply to "
8451 "-s ba; '" + s +
"' solves no reduction program");
8452 if (s !=
"ba" && k.level > 0)
8454 "--level is the hierarchy level of the SolverBA bound families and applies to -s ba; "
8455 "'" + s +
"' has no bound hierarchy");
8456 if (is_sim && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
8458 "--tol, --iter_tol and --iter_max do not apply to -s ssa: a sample path is not an "
8459 "iteration; use --samples to set its length");
8460 if (s ==
"mam" && k.iter_tol >= 0.0)
8462 "--iter_tol is not a SolverMAM option (MamOptions carries tol and iter_max); "
8464 if (s ==
"ag" && k.iter_tol >= 0.0)
8466 "--iter_tol is not a SolverAG option (AgOptions carries tol and iter_max, the "
8467 "tolerance and the sweep budget of the reversed-rate fixed point); use --tol");
8474 if (k.max_states >= 0 && s !=
"ag")
8476 "--max-states truncates the queue-length dimension of a SolverAG agent and applies "
8477 "to -s ag; '" + s +
"' truncates no agent (use --cutoff for a CTMC state space)");
8483 if (k.has_cutoff() && s !=
"ctmc" && !(s ==
"mam" && analysis ==
"prob") && s !=
"env")
8485 "--cutoff bounds the open population of a CTMC state space, and the level truncation "
8486 "of -s mam -a prob; '" + s +
" / " + analysis +
"' enumerates no states");
8492 if (!k.cutoff_mat.empty() && s !=
"ctmc")
8494 "--cutoff as a per-(station,class) matrix bounds an enumerated state space per "
8495 "station and applies to -s ctmc; '" + s +
"' takes one number");
8500 if (!k.stage_solver.empty() && s !=
"env")
8502 "--stage-solver names the solver each stage of a random environment is run with and "
8503 "applies to -s env; '" + s +
"' has no stages");
8504 if (!k.stage_solver.empty() && k.stage_solver !=
"fluid" && k.stage_solver !=
"ctmc" &&
8505 k.stage_solver !=
"mam")
8507 "--stage-solver '" + k.stage_solver +
8508 "' is not available: the environment coupling needs a TRANSIENT stage solve, and only "
8509 "the fluid analyzer, the enumerated CTMC and the flattened LD-QBD provide one in this "
8515 if (k.stage_solver ==
"mam" && k.method !=
"statevec")
8517 "--stage-solver mam applies to -s env --method statevec: the LD-QBD backend flattens "
8518 "its blocks into a generator the state-vector coupling propagates a distribution "
8519 "across, and the mean-field coupling carries marginal MEANS instead");
8524 if ((k.fj_accuracy > 0 || !k.fj_tmode.empty()) && s !=
"mam")
8526 "--fj-accuracy and --fj-tmode configure the FJ_codes fork-join approximation of "
8527 "solver_mam_fj.m and apply to -s mam; '" + s +
"' does not run it");
8532 if (!k.timescale.empty() && s !=
"mam")
8534 "--timescale selects the time scale of the MAM discrete-time path and applies to "
8535 "-s mam; '" + s +
"' does not read it (SolverNC takes --slotted)");
8554 if (analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
8555 analysis ==
"nodechain" || analysis ==
"cache" || analysis ==
"item") {
8556 const bool is_sim_engine = (s ==
"ssa" || s ==
"fluid");
8576 const bool cache_table = (analysis ==
"cache" || analysis ==
"item");
8577 const bool sim_cache_ok = (analysis ==
"cache");
8580 const bool ag_view = (s ==
"ag" && !cache_table);
8581 if ((s !=
"mva" && s !=
"auto" && s !=
"nc" && s !=
"mam" && s !=
"ba" && s !=
"ctmc" &&
8582 !ag_view && !is_sim_engine) ||
8583 (cache_table && is_sim_engine && !sim_cache_ok))
8589 ? std::string(
"-a node reports the per-node table of -s mva, nc, mam, ag, ba, "
8590 "ctmc, ssa, fluid, jmt, ldes and auto; '")
8591 :
"-a " + analysis +
8592 " is a view of the station AvgResult and is reported by -s mva, nc, "
8593 "mam, " + (analysis ==
"item" ?
"" :
"ag, ") +
"ba, ctmc" +
8594 (analysis ==
"item" ?
"" :
", ssa, fluid, jmt, ldes") +
" and auto; '") +
8595 s +
"' does not return the station AvgResult it is built from");
8601 if (is_sim_engine && arith !=
"double")
8603 "-a " + analysis +
" under -s " + s +
8604 " is read off a " + (s ==
"ssa" ?
"sample path" :
"fluid trajectory") +
8605 ", which is transcendental; rerun with --arith double (got '" + arith +
"')");
8606 const std::string eng = (s ==
"auto") ? std::string(
"mva") : s;
8607#define LINE_CLI_TABLE_LADDER(FN) \
8609 if (arith == "double") return FN<double>(file, k, eng); \
8610 if (arith == "exact") return FN<line::Rational>(file, k, eng); \
8611 if (arith == "real:16") return FN<line::Real<16> >(file, k, eng); \
8612 if (arith == "real" || arith == "real:32") return FN<line::Real<32> >(file, k, eng); \
8613 if (arith == "real:64") return FN<line::Real<64> >(file, k, eng); \
8614 if (arith == "real:128") return FN<line::Real<128> >(file, k, eng); \
8615 if (arith == "real:256") return FN<line::Real<256> >(file, k, eng); \
8623#undef LINE_CLI_TABLE_LADDER
8624 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8633 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"gen" && analysis !=
"states" &&
8634 analysis !=
"tran" && analysis !=
"tranprob" && analysis !=
"tranreward" &&
8635 analysis !=
"sample" && analysis !=
"reward" && analysis !=
"rewardvalue" &&
8636 analysis !=
"cdf" && analysis !=
"sens" && analysis !=
"firstpasst" &&
8637 analysis !=
"firstpasstmom")
8639 "the CTMC solver ports -a avg, node, sys, chain, nodechain, prob, gen, states, "
8640 "tran, tranprob, tranreward, sample, reward, reward-value, cdf, first-passt, "
8641 "first-passt-moments and sens (got '" + analysis +
"')");
8648 if ((k.method ==
"mdd" || k.method ==
"cftp" || k.method ==
"cftp.approx") &&
8651 "the '" + k.method +
8652 "' method never builds the explicit generator, so it serves -a avg only (got '" +
8653 analysis +
"'); use --method default for the state-space analyses");
8654 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0)
8656 "--tol, --iter_tol and --iter_max do not apply to -s ctmc: the stationary vector "
8657 "is obtained by a direct solve of pi Q = 0, with nothing to converge. The mdd "
8658 "method's level iteration has --mdd-tol and --mdd-maxiter of its own");
8662 if (arith ==
"double")
return solve_model_ctmc<double>(file, k, analysis);
8663 if (arith ==
"exact")
return solve_model_ctmc<line::Rational>(file, k, analysis);
8664 if (arith ==
"real:16")
return solve_model_ctmc<line::Real<16> >(file, k, analysis);
8665 if (arith ==
"real" || arith ==
"real:32")
8666 return solve_model_ctmc<line::Real<32> >(file, k, analysis);
8667 if (arith ==
"real:64")
return solve_model_ctmc<line::Real<64> >(file, k, analysis);
8668 if (arith ==
"real:128")
return solve_model_ctmc<line::Real<128> >(file, k, analysis);
8669 if (arith ==
"real:256")
return solve_model_ctmc<line::Real<256> >(file, k, analysis);
8670 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8682 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"cdf" &&
8683 analysis !=
"cdfpasst" && analysis !=
"perct" && analysis !=
"tran" &&
8684 analysis !=
"internals")
8686 "the MAM solver ports -a avg, node, prob, cdf, cdf-passt, perct-respt, tran and "
8687 "internals (got '" + analysis +
"')");
8688 if (arith !=
"double")
8690 "the MAM solver fits phase-type representations, whose fitter requires "
8691 "transcendental arithmetic; rerun with --arith double (got '" + arith +
"')");
8692 if (analysis ==
"tran" && k.t1 < 0.0)
8694 "-s mam -a tran integrates the transient queue length over a horizon and there is "
8695 "no default for it; pass --tspan t0 t1");
8696 if (analysis ==
"prob")
return solve_model_mam_prob<double>(file, k);
8697 if (analysis ==
"cdf")
return solve_model_mam_cdf<double>(file, k,
"cdf",
"CdfRespT");
8704 if (analysis ==
"cdfpasst")
8705 return solve_model_mam_cdf<double>(file, k,
"cdfpasst",
"CdfPassT");
8706 if (analysis ==
"perct")
return solve_model_mam_perct<double>(file, k);
8707 if (analysis ==
"tran")
return solve_model_mam_tran<double>(file, k);
8708 if (analysis ==
"internals")
return solve_model_mam_internals<double>(file, k);
8709 return solve_model_mam<double>(file, k);
8717 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"sample")
8720 if (arith !=
"double")
8722 "an SSA sample path is generated from exponential clocks, which are "
8723 "transcendental; rerun with --arith double (got '" + arith +
"')");
8724 if (analysis ==
"prob")
return solve_model_ssa_prob<double>(file, k);
8725 if (analysis ==
"sample")
return solve_model_ssa_sample<double>(file, k);
8726 return solve_model_ssa<double>(file, k);
8729 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"marg" &&
8730 analysis !=
"sysmarg" && analysis !=
"cdf" && analysis !=
"sens" &&
8731 analysis !=
"normconst" && analysis !=
"busyperiod")
8733 "the NC solver ports -a avg, -a node, -a prob, -a marg, -a sysmarg, -a cdf, "
8734 "-a sens, -a normconst and -a busyperiod (got '" + analysis +
"')");
8735 if (analysis ==
"busyperiod") {
8736 if (arith ==
"double")
return solve_model_nc_busyp<double>(file, k);
8737 if (arith ==
"exact")
return solve_model_nc_busyp<line::Rational>(file, k);
8738 if (arith ==
"real:16")
return solve_model_nc_busyp<line::Real<16> >(file, k);
8739 if (arith ==
"real" || arith ==
"real:32")
8740 return solve_model_nc_busyp<line::Real<32> >(file, k);
8741 if (arith ==
"real:64")
return solve_model_nc_busyp<line::Real<64> >(file, k);
8742 if (arith ==
"real:128")
return solve_model_nc_busyp<line::Real<128> >(file, k);
8743 if (arith ==
"real:256")
return solve_model_nc_busyp<line::Real<256> >(file, k);
8744 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8746 if (analysis ==
"sysmarg") {
8747 if (arith ==
"double")
return solve_model_nc_sysmarg<double>(file, k);
8748 if (arith ==
"exact")
return solve_model_nc_sysmarg<line::Rational>(file, k);
8749 if (arith ==
"real:16")
return solve_model_nc_sysmarg<line::Real<16> >(file, k);
8750 if (arith ==
"real" || arith ==
"real:32")
8751 return solve_model_nc_sysmarg<line::Real<32> >(file, k);
8752 if (arith ==
"real:64")
return solve_model_nc_sysmarg<line::Real<64> >(file, k);
8753 if (arith ==
"real:128")
return solve_model_nc_sysmarg<line::Real<128> >(file, k);
8754 if (arith ==
"real:256")
return solve_model_nc_sysmarg<line::Real<256> >(file, k);
8755 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8757 if (analysis ==
"marg") {
8758 if (arith ==
"double")
return solve_model_nc_marg<double>(file, k);
8759 if (arith ==
"exact")
return solve_model_nc_marg<line::Rational>(file, k);
8760 if (arith ==
"real:16")
return solve_model_nc_marg<line::Real<16> >(file, k);
8761 if (arith ==
"real" || arith ==
"real:32")
8762 return solve_model_nc_marg<line::Real<32> >(file, k);
8763 if (arith ==
"real:64")
return solve_model_nc_marg<line::Real<64> >(file, k);
8764 if (arith ==
"real:128")
return solve_model_nc_marg<line::Real<128> >(file, k);
8765 if (arith ==
"real:256")
return solve_model_nc_marg<line::Real<256> >(file, k);
8766 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8770 if (analysis ==
"normconst") {
8771 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
8772 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
8773 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
8774 if (arith ==
"real" || arith ==
"real:32")
8775 return solve_model_normconst<line::Real<32> >(file, k, s);
8776 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
8777 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
8778 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
8779 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8781 if (analysis ==
"sens") {
8782 if (arith ==
"double")
return solve_model_nc_sens<double>(file, k);
8783 if (arith ==
"exact")
return solve_model_nc_sens<line::Rational>(file, k);
8784 if (arith ==
"real:16")
return solve_model_nc_sens<line::Real<16> >(file, k);
8785 if (arith ==
"real" || arith ==
"real:32")
8786 return solve_model_nc_sens<line::Real<32> >(file, k);
8787 if (arith ==
"real:64")
return solve_model_nc_sens<line::Real<64> >(file, k);
8788 if (arith ==
"real:128")
return solve_model_nc_sens<line::Real<128> >(file, k);
8789 if (arith ==
"real:256")
return solve_model_nc_sens<line::Real<256> >(file, k);
8790 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8792 if (analysis ==
"cdf") {
8793 if (arith ==
"double")
return solve_model_nc_cdf<double>(file, k);
8794 if (arith ==
"exact")
return solve_model_nc_cdf<line::Rational>(file, k);
8795 if (arith ==
"real:16")
return solve_model_nc_cdf<line::Real<16> >(file, k);
8796 if (arith ==
"real" || arith ==
"real:32")
8797 return solve_model_nc_cdf<line::Real<32> >(file, k);
8798 if (arith ==
"real:64")
return solve_model_nc_cdf<line::Real<64> >(file, k);
8799 if (arith ==
"real:128")
return solve_model_nc_cdf<line::Real<128> >(file, k);
8800 if (arith ==
"real:256")
return solve_model_nc_cdf<line::Real<256> >(file, k);
8801 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8803 if (analysis ==
"prob") {
8804 if (arith ==
"double")
return solve_model_nc_prob<double>(file, k);
8805 if (arith ==
"exact")
return solve_model_nc_prob<line::Rational>(file, k);
8806 if (arith ==
"real:16")
return solve_model_nc_prob<line::Real<16> >(file, k);
8807 if (arith ==
"real" || arith ==
"real:32")
8808 return solve_model_nc_prob<line::Real<32> >(file, k);
8809 if (arith ==
"real:64")
return solve_model_nc_prob<line::Real<64> >(file, k);
8810 if (arith ==
"real:128")
return solve_model_nc_prob<line::Real<128> >(file, k);
8811 if (arith ==
"real:256")
return solve_model_nc_prob<line::Real<256> >(file, k);
8812 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8814 if (arith ==
"double")
return solve_model_nc<double>(file, k);
8815 if (arith ==
"exact")
return solve_model_nc<line::Rational>(file, k);
8816 if (arith ==
"real:16")
return solve_model_nc<line::Real<16> >(file, k);
8817 if (arith ==
"real" || arith ==
"real:32")
return solve_model_nc<line::Real<32> >(file, k);
8818 if (arith ==
"real:64")
return solve_model_nc<line::Real<64> >(file, k);
8819 if (arith ==
"real:128")
return solve_model_nc<line::Real<128> >(file, k);
8820 if (arith ==
"real:256")
return solve_model_nc<line::Real<256> >(file, k);
8821 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8829 if (analysis !=
"avg" && analysis !=
"cdf")
8831 "the AG solver ports -a avg (with its views -a node, -a sys, -a chain and "
8832 "-a nodechain) and -a cdf, the inherited exponential fallback: RCAT converges a "
8833 "fixed point over the synchronization rates and reports mean measures, forming no "
8834 "state probability or transient (got '" + analysis +
"')");
8835 if (analysis ==
"cdf") {
8836 if (arith ==
"double")
return solve_model_ag_cdf<double>(file, k);
8837 if (arith ==
"exact")
return solve_model_ag_cdf<line::Rational>(file, k);
8838 if (arith ==
"real:16")
return solve_model_ag_cdf<line::Real<16> >(file, k);
8839 if (arith ==
"real" || arith ==
"real:32")
8840 return solve_model_ag_cdf<line::Real<32> >(file, k);
8841 if (arith ==
"real:64")
return solve_model_ag_cdf<line::Real<64> >(file, k);
8842 if (arith ==
"real:128")
return solve_model_ag_cdf<line::Real<128> >(file, k);
8843 if (arith ==
"real:256")
return solve_model_ag_cdf<line::Real<256> >(file, k);
8844 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8846 if (arith ==
"double")
return solve_model_ag<double>(file, k);
8847 if (arith ==
"exact")
return solve_model_ag<line::Rational>(file, k);
8848 if (arith ==
"real:16")
return solve_model_ag<line::Real<16> >(file, k);
8849 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ag<line::Real<32> >(file, k);
8850 if (arith ==
"real:64")
return solve_model_ag<line::Real<64> >(file, k);
8851 if (arith ==
"real:128")
return solve_model_ag<line::Real<128> >(file, k);
8852 if (arith ==
"real:256")
return solve_model_ag<line::Real<256> >(file, k);
8853 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8856 if (analysis !=
"avg" && analysis !=
"bounds" && analysis !=
"cdf")
8858 "-a cdf, the inherited exponential fallback (got '" +
8860 if (analysis ==
"cdf") {
8861 if (arith ==
"double")
return solve_model_ba_cdf<double>(file, k);
8862 if (arith ==
"exact")
return solve_model_ba_cdf<line::Rational>(file, k);
8863 if (arith ==
"real:16")
return solve_model_ba_cdf<line::Real<16> >(file, k);
8864 if (arith ==
"real" || arith ==
"real:32")
8865 return solve_model_ba_cdf<line::Real<32> >(file, k);
8866 if (arith ==
"real:64")
return solve_model_ba_cdf<line::Real<64> >(file, k);
8867 if (arith ==
"real:128")
return solve_model_ba_cdf<line::Real<128> >(file, k);
8868 if (arith ==
"real:256")
return solve_model_ba_cdf<line::Real<256> >(file, k);
8869 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8871 if (analysis ==
"bounds") {
8872 if (arith ==
"double")
return solve_model_ba_bounds<double>(file, k);
8873 if (arith ==
"exact")
return solve_model_ba_bounds<line::Rational>(file, k);
8874 if (arith ==
"real:16")
return solve_model_ba_bounds<line::Real<16> >(file, k);
8875 if (arith ==
"real" || arith ==
"real:32")
8876 return solve_model_ba_bounds<line::Real<32> >(file, k);
8877 if (arith ==
"real:64")
return solve_model_ba_bounds<line::Real<64> >(file, k);
8878 if (arith ==
"real:128")
return solve_model_ba_bounds<line::Real<128> >(file, k);
8879 if (arith ==
"real:256")
return solve_model_ba_bounds<line::Real<256> >(file, k);
8880 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8882 if (arith ==
"double")
return solve_model_ba<double>(file, k);
8883 if (arith ==
"exact")
return solve_model_ba<line::Rational>(file, k);
8884 if (arith ==
"real:16")
return solve_model_ba<line::Real<16> >(file, k);
8885 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ba<line::Real<32> >(file, k);
8886 if (arith ==
"real:64")
return solve_model_ba<line::Real<64> >(file, k);
8887 if (arith ==
"real:128")
return solve_model_ba<line::Real<128> >(file, k);
8888 if (arith ==
"real:256")
return solve_model_ba<line::Real<256> >(file, k);
8889 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8892 if (analysis !=
"avg" && analysis !=
"cdf")
8894 "SolverQNS reports -a avg and -a cdf, the inherited exponential fallback: "
8895 "qnsolver returns one chain-level table of means and computes no state "
8896 "probability and no transient (got '" + analysis +
"')");
8900 if (arith !=
"double")
8902 "SolverQNS reads its results back as the decimal text an external binary printed, "
8903 "which is double at best; --arith " + arith +
8904 " would report a precision the tool never produced");
8905 if (analysis ==
"cdf")
return solve_model_qns_cdf(file, k);
8906 return solve_model_qns<double>(file, k);
8908 if (s ==
"fluid" || s ==
"fld") {
8909 if (analysis !=
"avg" && analysis !=
"odes" && analysis !=
"var" &&
8910 analysis !=
"tranvar" && analysis !=
"jacobian" && analysis !=
"tran" &&
8911 analysis !=
"prob" && analysis !=
"cdf" && analysis !=
"aoi" &&
8912 analysis !=
"statevec")
8914 "the fluid solver ports -a avg, -a tran, -a tranvar, -a prob, -a cdf, -a aoi, "
8915 "-a odes, -a statevec, -a var and -a jacobian (got '" + analysis +
"')");
8918 if (arith !=
"double")
8920 "the fluid solver integrates its drift with LSODA, which is double precision by "
8921 "construction; rerun with --arith double (got '" + arith +
"')");
8922 if (analysis ==
"odes")
return solve_model_fluid_odes<double>(file, k);
8923 if (analysis ==
"statevec")
return solve_model_fluid_statevec<double>(file, k);
8924 if (analysis ==
"jacobian")
return solve_model_fluid_jacobian<double>(file, k);
8925 if (analysis ==
"var")
return solve_model_fluid_var<double>(file, k);
8926 if (analysis ==
"tranvar")
return solve_model_fluid_tranvar<double>(file, k);
8927 if (analysis ==
"tran")
return solve_model_fluid_tran<double>(file, k);
8928 if (analysis ==
"prob")
return solve_model_fluid_prob<double>(file, k);
8929 if (analysis ==
"cdf")
return solve_model_fluid_cdf<double>(file, k);
8930 if (analysis ==
"aoi")
return solve_model_fluid_aoi<double>(file, k);
8931 return solve_model_fluid<double>(file, k);
8933 if (analysis ==
"prob") {
8934 if (arith ==
"double")
return solve_model_prob<double>(file, k);
8935 if (arith ==
"exact")
return solve_model_prob<line::Rational>(file, k);
8936 if (arith ==
"real:16")
return solve_model_prob<line::Real<16> >(file, k);
8937 if (arith ==
"real" || arith ==
"real:32")
return solve_model_prob<line::Real<32> >(file, k);
8938 if (arith ==
"real:64")
return solve_model_prob<line::Real<64> >(file, k);
8939 if (arith ==
"real:128")
return solve_model_prob<line::Real<128> >(file, k);
8940 if (arith ==
"real:256")
return solve_model_prob<line::Real<256> >(file, k);
8941 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8943 if (analysis ==
"marg") {
8944 if (arith ==
"double")
return solve_model_marg<double>(file, k);
8945 if (arith ==
"exact")
return solve_model_marg<line::Rational>(file, k);
8946 if (arith ==
"real:16")
return solve_model_marg<line::Real<16> >(file, k);
8947 if (arith ==
"real" || arith ==
"real:32")
return solve_model_marg<line::Real<32> >(file, k);
8948 if (arith ==
"real:64")
return solve_model_marg<line::Real<64> >(file, k);
8949 if (arith ==
"real:128")
return solve_model_marg<line::Real<128> >(file, k);
8950 if (arith ==
"real:256")
return solve_model_marg<line::Real<256> >(file, k);
8951 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8953 if (analysis ==
"normconst") {
8954 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
8955 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
8956 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
8957 if (arith ==
"real" || arith ==
"real:32")
8958 return solve_model_normconst<line::Real<32> >(file, k, s);
8959 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
8960 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
8961 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
8962 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8964 if (analysis ==
"cdf") {
8967 if (arith ==
"double")
return solve_model_mva_cdf<double>(file, k);
8968 if (arith ==
"exact")
return solve_model_mva_cdf<line::Rational>(file, k);
8969 if (arith ==
"real:16")
return solve_model_mva_cdf<line::Real<16> >(file, k);
8970 if (arith ==
"real" || arith ==
"real:32")
8971 return solve_model_mva_cdf<line::Real<32> >(file, k);
8972 if (arith ==
"real:64")
return solve_model_mva_cdf<line::Real<64> >(file, k);
8973 if (arith ==
"real:128")
return solve_model_mva_cdf<line::Real<128> >(file, k);
8974 if (arith ==
"real:256")
return solve_model_mva_cdf<line::Real<256> >(file, k);
8975 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8977 if (analysis !=
"avg")
8979 "the model-solving path ports -a avg, -a node, -a prob, -a marg, -a normconst and "
8987 if (arith ==
"double")
return solve_model_mva<double>(file, k);
8988 if (arith ==
"exact")
return solve_model_mva<line::Rational>(file, k);
8989 if (arith ==
"real:16")
return solve_model_mva<line::Real<16> >(file, k);
8990 if (arith ==
"real" || arith ==
"real:32")
return solve_model_mva<line::Real<32> >(file, k);
8991 if (arith ==
"real:64")
return solve_model_mva<line::Real<64> >(file, k);
8992 if (arith ==
"real:128")
return solve_model_mva<line::Real<128> >(file, k);
8993 if (arith ==
"real:256")
return solve_model_mva<line::Real<256> >(file, k);
8995 "--arith '" + arith +
8996 "' is not a model-solve backend; use double, exact or real:<16|32|64|128|256>");
9006void print_brief_help() {
9008 "LINE solver (C++), version %s\n"
9010 "Usage: line-cli -f <model> [-s <solver>] [-a <analysis>] [-o <format>]\n"
9011 " cat model.json | line-cli\n"
9014 " -f, --file <path> model file: .json (network), .lqnx (layered),\n"
9015 " .jsimg (JMT), .pnml (Petri net); stdin if omitted\n"
9016 " -s, --solver <name> auto (default), mva, nc, ctmc, mam, fluid, ssa,\n"
9017 " ldes, jmt, ag, ba, uq; ln for layered models, env\n"
9018 " for random environments\n"
9019 " -a, --analysis <type> avg (default), node, sys, chain, tran, prob, cdf,\n"
9020 " states, sample, normconst, bounds, ... (comma list)\n"
9021 " -o, --output <fmt> readable (default) | json\n"
9022 " --method <name> algorithm within the chosen solver\n"
9023 " --samples <n> simulation run length (ssa, ldes); default 10000\n"
9024 " --seed <n> random seed; default 23000\n"
9025 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
9026 " the solver console, a running progress log\n"
9027 " --find-solver [m] which solvers and methods can analyze this model,\n"
9028 " optionally only those answering measure <m>\n"
9029 " (avg, tran, cdf, prob, sample, ...); reports and exits\n"
9030 " --find-solver-all [m] the same, keeping the refused pairs and the\n"
9031 " reason each was refused\n"
9032 " -h, --help this message\n"
9033 " --help-all every option, solver by solver\n"
9034 " -V, --version version string\n"
9035 " --install environment check: which optional backends\n"
9036 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
9039 " line-cli -f model.json solve, letting auto pick the solver\n"
9040 " line-cli -f model.json -s mva -a avg mean queue lengths, MVA\n"
9041 " line-cli -f model.lqnx -s ln solve a layered model\n"
9042 " line-cli -f model.json -s ssa --samples 1e6 -o json\n"
9043 " line-cli -f model.json --find-solver what can solve this model\n"
9044 " line-cli -f model.json --find-solver cdf ... and return a passage-time law\n"
9046 "Each solver has flags of its own (tolerances, horizons, engine choices):\n"
9047 "run `line-cli --help-all` for the full reference.\n",
9053 "LINE multiprecision solver (C++), version %s\n"
9055 "Usage: line-cli [OPTIONS]\n"
9056 " cat model.json | line-cli -i json [OPTIONS]\n"
9057 " line-cli model.lqnx [OPTIONS]\n"
9059 "Options (flag-compatible with jline.cli.LineCLI):\n"
9060 " -f, --file <path> model file; stdin when omitted (json only)\n"
9061 " -i, --input <fmt> input format: json | jsim | jsimg | jsimw |\n"
9062 " lqnx | xml | pnml. Without it a .lqnx or .xml\n"
9063 " path is read as a layered model, a\n"
9064 " .jsim/.jsimg/.jsimw path as a JMT simulation\n"
9065 " document, a .pnml path as a place/transition\n"
9066 " net (ISO/IEC 15909-2), and anything else as a\n"
9067 " Network model.json. The three jsim spellings\n"
9068 " name ONE format, as they do in JMT\n"
9069 " -o, --output <fmt> output format: readable | json (| layers, lqnx).\n"
9070 " json is honoured by EVERY analysis, not only\n"
9071 " -a avg: each answers under a key named after\n"
9072 " its -a, with the arithmetic and the resolved\n"
9073 " method beside it, and every index inside a\n"
9074 " payload is 0-based against the tables' 1-based\n"
9075 " columns (each payload states its indexBase)\n"
9076 " -s, --solver <name> Network: auto, mva, nc, ctmc, mam, ba, ssa, fluid, uq,\n"
9077 " ldes (the SSJ discrete-event engine, run as a\n"
9078 " subprocess on common/ldes or common/ldes.jar),\n"
9079 " jmt (the Java Modelling Tools engine),\n"
9080 " qns (the external qnsolver binary)\n"
9081 " layered: auto, ln, ln.mva, ln.comom, lqns,\n"
9082 " ldes (the native in-process LN simulator, a\n"
9083 " sample path of the layered model itself and\n"
9084 " not a decomposition into layers; it takes\n"
9085 " --samples and --seed, and NOT the --ldes-*\n"
9086 " family, which configures the subprocess\n"
9087 " engine that answers -s ldes on a Network)\n"
9088 " environment: env (an Environment model.json)\n"
9089 " -a, --analysis <type> analysis: avg, node, sys, chain, nodechain,\n"
9090 " stage, cache, item, prob, marg, cdf, cdf-passt,\n"
9091 " perct-respt, tran, tran-cdf-respt,\n"
9092 " tran-cdf-passt, tranprob, tranreward,\n"
9093 " reward-value, normconst, gen, states, sample,\n"
9094 " reward, sens, first-passt, odes, statevec,\n"
9095 " var, tranvar, busyperiod,\n"
9096 " jacobian, aoi, internals, bounds, posterior,\n"
9097 " interval. A COMMA LIST runs several in order\n"
9098 " (`-a avg,sys`), emitting one -o json envelope\n"
9099 " per analysis rather than one merged object.\n"
9100 " The JAR CLI's own spellings are accepted as\n"
9101 " aliases -- cdf-respt, prob-sys-aggr, tran-avg,\n"
9102 " generator, reward-steady, all -- and collapse\n"
9103 " onto the arm that already answers them whole:\n"
9104 " -a prob reports getProbSys, getProbSysAggr and\n"
9105 " the per-station pair together, -a sample walks\n"
9106 " all four samplers at once, and -a stage IS\n"
9107 " -a avg on a Network (one implicit stage).\n"
9109 " solver serves is stated by its own refusal;\n"
9110 " ssa serves avg, prob and sample (prob and\n"
9111 " sample run the SERIAL engine whatever -m\n"
9112 " said, since the NRM simulates counts rather\n"
9113 " than the state encoding)\n"
9114 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
9115 " the solver console, a running progress log\n"
9116 " of every solver run\n"
9117 " -d, --seed <n> random seed (SSA, ctmc --method cftp); default\n"
9118 " 23000. -d is the JAR CLI's spelling\n"
9119 " --warmupfrac <f> SSA: leading fraction of the path discarded\n"
9120 " before the means are taken, in [0,1)\n"
9121 " --pstar <p> fluid: exponent of the p-norm smoothing of the\n"
9122 " drift; without it the hard min() is integrated\n"
9123 " --busyperiod <n,..> -a busyperiod: the orders wanted; default 1\n"
9124 " --busyperiod-subnet <i,..>\n"
9125 " -a busyperiod: the 1-based stations forming the\n"
9126 " subnetwork. Required -- a busy period is defined\n"
9127 " for a NAMED set and no default can choose one\n"
9128 " --method <name> algorithm within the solver\n"
9129 " --samples <n> simulation run length (SSA); default 10000.\n"
9130 " Accepts 1e6 as well as 1000000. A simulation\n"
9131 " figure is only a measurement WITH this number,\n"
9132 " which is why the SSA banner reports it back.\n"
9133 " REQUIRED by ctmc --method cftp, where the draw\n"
9134 " is the answer rather than a run length.\n"
9135 " --mdd-tol <x> level-iteration tolerance of ctmc --method mdd;\n"
9136 " default 1e-12. NOT --tol: that iteration is an\n"
9137 " inner solve whose fixed point is checked\n"
9138 " against the population invariant at 1e-6, so a\n"
9139 " solver-sized tolerance stops short of it\n"
9140 " --mdd-maxiter <n> coupled sweeps before ctmc --method mdd is\n"
9141 " declared non-convergent; default 500\n"
9142 " --level <n> hierarchy level of the ba pbh/cbh/sib families\n"
9143 " and the iteration count of pbk/bjbk; default 2\n"
9144 " --qrf-params <j> JSON (inline or a path) with the QRF blocking\n"
9145 " tables of -s ba --method qrf.bas|qrf.rsrd:\n"
9146 " f, MR, BB, MM, MM1, ZZ and optionally F, the\n"
9147 " fields sn_to_qrf_params assembles. ZM is\n"
9148 " derived from ZZ. There is no default: assuming\n"
9149 " no blocking puts the bound ~31x farther from\n"
9150 " exact, so its absence is refused\n"
9151 " --qrf-alpha <j> JSON (nstations x N) load-dependent scaling of\n"
9152 " the ba qrf.mmi.ld, qrf.mmi.linear and qrf.rsrd\n"
9153 " arms; default all ones\n"
9154 " --tol <x> convergence tolerance (mva, nc, mam, ag, fluid)\n"
9155 " --iter_tol <x> outer-loop tolerance (mva, nc, fluid)\n"
9156 " --iter_max <n> iteration cap (mva, nc, mam, ag, fluid)\n"
9157 " --max-states <n> truncation level of an OPEN agent's queue-length\n"
9158 " dimension (ag), options.config.maxStates;\n"
9159 " default 100. A closed class is bounded by its\n"
9160 " own population instead, and --method inapinf\n"
9161 " ignores the level and solves the open agents on\n"
9162 " the infinite state space\n"
9163 " --fork-join <arm> which fork-join transform the mean-value fixed\n"
9164 " point takes (mva, nc): default|mmt|fjt is the\n"
9165 " MMT transform, ht|heidelberger-trivedi the\n"
9166 " Heidelberger-Trivedi one, which is CLOSED\n"
9167 " models only and a different answer to the same\n"
9168 " model rather than a faster route to one\n"
9169 " --cutoff <n|matrix> open jobs per class in the CTMC state space;\n"
9170 " a matrix is per (station,class), '1,1,0;3,3,0;0,0,3'\n"
9171 " (ctmc); without it the reference's\n"
9172 " ceil(6000^(1/(M*K))) is used and reported.\n"
9173 " Also the level truncation of mam -a prob,\n"
9174 " whose open queue has no bound of its own\n"
9175 " --fj-accuracy <n> FJ_codes truncation C of the queue-length\n"
9176 " difference between the two fork-join\n"
9177 " branches (mam, homogeneous fork-join);\n"
9178 " default 100, larger is more accurate\n"
9179 " --fj-tmode <mode> how that approximation solves for its T\n"
9180 " matrix: NARE (default) or Sylves\n"
9181 " --timescale <mode> auto (default), discrete or continuous: how\n"
9182 " -s mam reads the time scale. auto lets the\n"
9183 " distributions decide; discrete raises rather\n"
9184 " than solve a model that mixes lattice and\n"
9185 " non-lattice laws. The slot is --slotlength\n"
9186 " --tspan <t0>:<t1> transient horizon (ctmc -a tranprob,\n"
9187 " mam -a tran, fluid); a bare <t1> starts at 0.\n"
9188 " For the CTMC and MAM there is no default:\n"
9189 " pi(t) on an unstated horizon is not a\n"
9190 " quantity. For the fluid solver it bounds the\n"
9191 " integration, and is what -s fluid --method kp\n"
9192 " reports its covariance AT\n"
9193 " -n, --node <n> 1-based stateful node a state query is\n"
9194 " labelled by (ctmc -a tranprob, -a sample and\n"
9195 " ssa -a sample), the\n"
9196 " queue mam -a prob reports, or the station\n"
9197 " mva -a marg reports; without it the\n"
9198 " whole network is reported (the MAM queries\n"
9199 " take the model's only Queue)\n"
9200 " -c, --class <r> 1-based job class of mva -a marg; without it\n"
9201 " every class is reported\n"
9202 " NOTE ON THE INDEX BASE: -n and -c are 1-BASED\n"
9203 " here, as every station index this CLI takes\n"
9204 " is, and 0-BASED in jline.cli.LineCLI, which\n"
9205 " indexes as Java does. The short spellings are\n"
9206 " accepted so one command line parses in both,\n"
9207 " but the SAME number names a different node --\n"
9208 " the two bridges (cpp_dispatch, jar_dispatch)\n"
9209 " each convert for their own CLI\n"
9210 " --marg-states <ns> comma-separated job counts the mva -a marg\n"
9211 " curve is evaluated at, the reference's\n"
9212 " state_m; without it each law takes its own\n"
9213 " default range (0..N_r closed, mean + 5 sigma\n"
9214 " Poisson, the 1e-10 tail geometric)\n"
9215 " --notation <form> scalar (default) | matrix, the form the ODE\n"
9216 " export writes (fluid -a odes only)\n"
9217 " --symbolic <b> computer-algebra backend of fluid -a\n"
9218 " jacobian: auto (default, searches for a\n"
9219 " line-sage-rest service), a URL, an image\n"
9220 " name, or none to differentiate locally\n"
9221 " --equilibria also ask the backend for the solutions of\n"
9222 " f(x) = 0 (fluid -a jacobian). Needs a\n"
9223 " backend: solving is not differentiating\n"
9224 " --cdf-algorithm <a> exact (default, pfqn_stdf) | rd\n"
9225 " (pfqn_stdf_heur), how the sojourn law is\n"
9226 " inverted (nc -a cdf only)\n"
9227 " --perm-engine <e> exact (default, Ryser) | spm | bethe | heur |\n"
9228 " huberlaw | adapart, the permanent estimator\n"
9229 " of nc -a sysmarg. The five approximations\n"
9230 " refuse a demand matrix with a zero entry;\n"
9231 " spm is the saddle point, whose cost does not\n"
9232 " grow with the population\n"
9233 " --tran-points <n> points on the uniform transient grid the ENV\n"
9234 " mean-field coupling sums its stage exit\n"
9235 " metrics over (env only); default 1001\n"
9236 " --state <n,...> the ENCODED state row of the node named by\n"
9237 " --node that -a prob asks about, i.e.\n"
9238 " getProb(node, state)'s second argument; without\n"
9239 " it the query is about the model's default\n"
9241 " --events <n> length of ONE sampled trajectory (-a sample);\n"
9242 " default 1000. NOT --samples, which is a\n"
9243 " solver's run length\n"
9244 " --timestep <dt> fixed output step of a transient CTMC solve\n"
9245 " --transient-method <m> ode (default) or fau, how the CTMC forward\n"
9246 " equation is advanced over the output grid\n"
9247 " --fau-epsilon <e> fau: probability mass the grid may discard\n"
9248 " --fau-delta <d> fau: occupancy below which a state is dropped\n"
9249 " (-a tranprob, -a tranreward), options.timestep\n"
9250 " of ctmc_transient.m; without it the grid is the\n"
9251 " integrator's own adaptive one. It changes WHERE\n"
9252 " the solution is reported, not how it is\n"
9253 " computed: the grid points are read off the same\n"
9255 " --percentiles <p,..> levels getPerctRespT is read at (-s mam),\n"
9256 " as fractions (0.9) or percents (90); default\n"
9257 " 0.50,0.90,0.95,0.99, the reference's pers_stored\n"
9258 " --reward-name <nm> which declared reward -a reward-value returns\n"
9259 " the value function of. REQUIRED there and never\n"
9260 " defaulted: two rewards have different value\n"
9261 " functions, and picking one would mislabel it\n"
9262 " -p, --port <n> run as a solve SERVER on this port, speaking\n"
9263 " LineWebSocketServer's protocol: one WebSocket\n"
9264 " text message per connection, its first line the\n"
9265 " comma-separated argument list and its remainder\n"
9266 " the model document; the CLI's output comes back\n"
9267 " as one text message. Plaintext, one connection\n"
9268 " at a time, and -f is refused beside it\n"
9269 " -m, --maxreq <n> quit after serving n requests; without it the\n"
9270 " server runs until interrupted\n"
9271 " -h, --help the short message: the flags a first run needs\n"
9272 " --help-all this message, every option solver by solver\n"
9273 " -V, --version version string\n"
9274 " --install environment check: report which optional backends\n"
9275 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
9277 "Layered models (-i lqnx|xml) additionally take:\n"
9278 " --layer-solver <s> solver run in each layer: mva (default)|nc|\n"
9279 " fluid|ssa, the C++ spelling of the reference's\n"
9280 " factory argument: LN(model, @(m) MVA(m))\n"
9281 " against LN(model, @(m) Fluid(m)). They converge\n"
9282 " to DIFFERENT fixed points, not to the same one\n"
9283 " by different routes, because each layer's\n"
9284 " results feed the next outer iteration's demands.\n"
9285 " fluid is double only and refuses a layer with a\n"
9286 " fork; ssa is NOISY, so the outer loop switches\n"
9287 " to the Robbins-Monro / Polyak-Ruppert controller\n"
9288 " --method <name> the LN update: default | moment3 | mwba.upper |\n"
9289 " mwba.lower. moment3 fits an APH to each layer's\n"
9290 " response-time CDF and convolves along the entry,\n"
9291 " which is what makes -a cdf possible; mwba.*\n"
9292 " reports Majumdar-Woodside box BOUNDS on\n"
9293 " throughput and processor utilization and solves\n"
9294 " no layer at all (every other metric is NaN)\n"
9295 " --ln-transient <m> coupled (default) | decoupled, how -a tran\n"
9296 " couples the layers. decoupled freezes the\n"
9297 " inter-layer demands at the fixed point; coupled\n"
9298 " relaxes time-varying demands through the fluid\n"
9299 " rate schedule until the trajectories settle\n"
9300 " --ln-transient-channels <c> both (default) | thinkt | callservt,\n"
9301 " which inter-layer coupling the relaxation\n"
9302 " injects, for isolating one channel's share\n"
9303 " --sens-method <m> auto (default) | exact | fd, the branch each\n"
9304 " LAYER's sensitivity table takes (-a sens);\n"
9305 " the same three under -s nc -a sens\n"
9306 " --sens-scheme <s> forward (default) | central, the difference\n"
9307 " quotient of the fd branch\n"
9308 " --sens-step <h> relative rate perturbation of the fd branch,\n"
9309 " in (0,1); default 1e-4, or 1e-2 for ssa layers\n"
9310 " --no-interlocking disable the interlocking correction\n"
9311 " --repeat <k> re-solve k times, report the best wall clock\n"
9312 " -o layers dump every layer's stations, classes, rates\n"
9313 " and routing instead of the AvgTable\n"
9314 " -a takes avg (getAvgTable), tran (getTranAvg, needs --tspan and fluid\n"
9315 " layers), sens (getSensitivityTable) and cdf (getCdfRespT, moment3).\n"
9316 " --iter_max and --iter_tol set the outer LN loop; --tol does not apply,\n"
9317 " and neither does any Network solver token.\n"
9319 "The external LQNS binary (-s lqns) additionally takes:\n"
9320 " --method <name> default | lqns | srvn | exactmva |\n"
9321 " srvn.exactmva | sim | lqsim | lqnsdefault.\n"
9322 " sim and lqsim run lqsim, the SIMULATOR;\n"
9323 " lqnsdefault is lqns with no pragma at all,\n"
9324 " which is a different fixed point and not a\n"
9325 " synonym for default\n"
9326 " --multiserver <p> conway|rolia|zhou|suri|reiser|schmidt|default\n"
9327 " (= rolia), the -Pmultiserver= pragma. Not\n"
9328 " passed to lqsim, which has no MVA to configure\n"
9329 " --samples <n> lqsim run length (-A); default 10000\n"
9330 " --timeout <s> kill the child after s seconds; without it the\n"
9332 " --keep keep the working directory with model.lqnx and\n"
9333 " model.lqxo instead of removing it\n"
9334 " --verbose echo the command line and the binary's output\n"
9335 " --remote[-url <u>] solve on a host running lqns-rest instead of\n"
9336 " locally; -url implies --remote. LINE ships no\n"
9337 " LQNS binary, so this is the other way to reach\n"
9339 " -a takes avg only: lqns computes no transient, no sensitivity and no\n"
9340 " response-time distribution. QLen is the element utilization, Util its\n"
9341 " processor utilization per server, RespT its phase-1 service time;\n"
9342 " ResidT and ArvR print NaN because lqns reports neither.\n"
9344 "The external qnsolver binary (-s qns) additionally takes:\n"
9345 " --method <name> default (= rolia) | conway | rolia | zhou |\n"
9346 " reiser. The multiserver approximation, passed\n"
9347 " as qnsolver -m and only when the model HAS a\n"
9348 " multiserver station. suri and schmidt are\n"
9349 " listed by the reference but reach the tool\n"
9350 " through its SolverLQNS branch, which this port\n"
9351 " does not carry, so they are refused by name\n"
9352 " --multiserver <p> the same choice under its config spelling;\n"
9353 " --method wins when it names one\n"
9354 " --timeout <s> kill the child after s seconds; without it the\n"
9356 " --keep keep the working directory with model.jmva and\n"
9357 " result.jmva instead of removing it\n"
9358 " -a takes avg only. The model is marshalled to the JMVA interchange\n"
9359 " format at CHAIN level and the chain results are de-aggregated back to\n"
9360 " classes, so only Queue, Delay and Source stations are expressible; any\n"
9361 " other station is refused rather than dropped. A closed model that is\n"
9362 " NOT product-form is refused too: the reference converts it with QN2LQN\n"
9363 " and delegates to SolverLQNS, and QN2LQN is not ported. --arith is\n"
9364 " double only, since the results arrive as the text an external binary\n"
9367 "Discrete-event simulation (-s ldes) additionally takes:\n"
9368 " --ldes-tranfilter <f> warmup filter: mser5 (default), fixed, none\n"
9369 " --ldes-warmupfrac <x> fraction the fixed filter discards (0.2)\n"
9370 " --ldes-cimethod <m> CI estimator: obm (default), bm, spectral,\n"
9372 " --ldes-cnvgon stop on relative precision instead of on\n"
9373 " the --samples budget\n"
9374 " --ldes-cnvgtol <x> that precision target (0.05); implies\n"
9376 " --slotted run the analytical solver on a discrete\n"
9377 " (slotted) time scale; SolverNC routes to the\n"
9378 " discrete-time product form and refuses a model\n"
9380 " --slotlength <x> the slot in model time units; implies --slotted\n"
9381 " --ldes-slotted run on a discrete (slotted) time scale; a\n"
9382 " sample off the lattice is an error, never\n"
9384 " --ldes-slotlength <x> the slot (1.0); implies --ldes-slotted\n"
9385 " --ldes-replications <n> independent runs, averaged. A single path\n"
9386 " is NOT E[N](t): -a tran over an ensemble\n"
9387 " mean needs this\n"
9388 " --ldes-numthreads <n> workers for those replications\n"
9389 " --ldes-maxtime <s> wall-clock budget; the engine stops early\n"
9390 " and reports stopping=max_time\n"
9391 " --ldes-initsol <v,..> warm-start placement, station-major\n"
9392 " [st0_cl0, st0_cl1, ...]. Add --ldes-tranfilter\n"
9393 " fixed --ldes-warmupfrac 0 to reproduce\n"
9394 " initFromSolver, which assumes the placement\n"
9395 " is already a steady state\n"
9396 " --ldes-rest-url <u> solve on an LDES REST server instead of a\n"
9397 " local binary; same wire format, same numbers\n"
9398 " The model.json is forwarded to the engine BYTE FOR BYTE, so a model\n"
9399 " this port cannot itself parse (a cache with retrieval, an SPN, a\n"
9400 " polling server) is simulated exactly as the MATLAB and Python clients\n"
9401 " simulate it. -a avg, tran, cdf (the empirical response-time law),\n"
9402 " sample and reward are served; -a prob is refused, because getProbSys\n"
9403 " weighs the trajectory against the model's current state and the C++\n"
9404 " NetworkStruct carries no such row. --arith is double only.\n"
9406 "Uncertainty quantification (-s uq) additionally takes:\n"
9407 " --uq-solver <s> the engine run at each design point: mva, nc,\n"
9408 " mam, ba, ctmc, fluid or ssa. REQUIRED: UQ\n"
9409 " computes nothing itself, and defaulting it\n"
9410 " would attribute the numbers to an engine the\n"
9411 " caller never chose\n"
9412 " A model whose service or arrival process is a Prior is a FAMILY of\n"
9413 " models. UQ discretizes each Prior, solves the tensor product of the\n"
9414 " alternatives, and reports the prior-weighted expectation. Under -s uq\n"
9415 " three flags describe the DESIGN and not the engine: --method is\n"
9416 " quadrature (default, and the alias of discrete) or montecarlo,\n"
9417 " --samples the nodes per continuous Prior (11), --seed the Monte Carlo\n"
9418 " stream. The stage solver therefore keeps its own sample count and its\n"
9419 " own seed; --tol, --iter_tol, --iter_max and --cutoff pass through to\n"
9420 " it, since UQ has no convergence of its own. -a posterior prints every\n"
9421 " design point, its weight and the means it substituted, which is what\n"
9422 " says whether the expectation averaged two nearby models or two very\n"
9423 " different ones. Every other solver REFUSES a model carrying a Prior\n"
9424 " rather than lowering it to its mixture moments.\n"
9425 " -a interval answers the OTHER epistemic question, in which a\n"
9426 " parameter is bounded but not distributed: it drops the weights and\n"
9427 " keeps the endpoints. On a single-class closed model of LI\n"
9428 " single-server queues and delays it is the EXACT hull of MVA over the\n"
9429 " demand box (2*(m+2) MVA calls, no design solved at all); otherwise it\n"
9430 " falls back to the range over the solved design points, which for a\n"
9431 " continuous Prior lies strictly inside the true range. The table says\n"
9432 " which, and the fallback warns on stderr: a range that is not an\n"
9433 " enclosure must not read like one.\n"
9435 "Random environments (-s env) read an Environment model.json, whose\n"
9436 " stages each hold a Network and whose transitions carry the stage\n"
9437 " holding times. The stages are solved TRANSIENTLY and coupled: each\n"
9438 " stage starts from the queue lengths the previous one left, and the\n"
9439 " reported means are the per-stage sojourn averages blended by the\n"
9440 " environment probabilities. --method selects the coupling: meanfield\n"
9441 " (default, the reference's, carries the marginal means across a\n"
9442 " switch) or statevec|blend (carries the whole joint distribution).\n"
9443 " meanfield solves each stage with the fluid transient and is double\n"
9444 " only; statevec uniformizes a CTMC and takes the whole --arith ladder.\n"
9445 " --method avg|dec asks instead for a closed-form limit, which carries\n"
9446 " nothing across a switch and iterates nothing: avg solves ONE model\n"
9447 " whose modulated rates are their probEnv-weighted averages (exact as\n"
9448 " the environment gets fast), dec solves each stage in steady state and\n"
9449 " blends by probEnv (exact as it gets slow). A model with an\n"
9450 " environment-declared node breakdown is read from the nodeFailures\n"
9451 " block, in the expanded or the one-stage macro form.\n"
9452 " --tspan bounds the stage horizon and --tran-points its grid; --iter_tol\n"
9453 " and --iter_max drive the fixed point. RespT and ArvR print as nan\n"
9454 " because ENV computes neither -- the reference returns them as NaN too,\n"
9455 " and ResidT carries QLen/Tput.\n"
9457 "Additions specific to this port:\n"
9458 " --arith <mode> double (default) | exact | real:<digits>\n"
9459 " --list-api list the API functions ported so far\n"
9460 " --api <name> invoke one API function directly\n"
9461 " --args <path> JSON arguments for --api; stdin when omitted\n"
9463 "Solvers and what they honour. Every model-solving solver reads -a avg,\n"
9464 "-f/-i and --method; nothing else is wired, so tolerances, iteration\n"
9465 "caps, seeds and sample counts keep their SolverOptions defaults rather\n"
9466 "than being invented here. mva and nc additionally read -a prob -- mva\n"
9467 "fits a binomial to its own means, nc returns the exact product-form\n"
9468 "probability, so the two disagree by construction. mva also reads -a\n"
9469 "marg, @SolverMVA's getProbMarg: P(n jobs of class r at station i) for\n"
9470 "every (station, class) pair, narrowed by --node / --class and evaluated\n"
9471 "at --marg-states. A closed class takes the Schmidt binomial fitted to\n"
9472 "Q(i,r); an open one takes the station's exact BCMP marginal (Poisson at\n"
9473 "an infinite server, multinomial-geometric at a queue). Both solvers read\n"
9474 "-a normconst, getProbNormConstAggr: nc reports the constant its solve\n"
9475 "already formed, mva RE-ENTERS its analyzer at method='exact', since only\n"
9476 "the exact recursion carries a G -- a model whose branch forms none, an\n"
9477 "open or mixed one above all, reports nan, as the reference does. nc also\n"
9479 "@SolverNC's getCdfRespT: the whole response-time law per (station,\n"
9480 "class) on one logarithmic grid, FCFS stations only, with\n"
9481 "--cdf-algorithm exact (pfqn_stdf, the default) or rd (pfqn_stdf_heur).\n"
9482 "nc reads -a sens as well, @NetworkSolver's getSensitivityTable: the\n"
9483 "derivative of each row's means with respect to its own service rate,\n"
9484 "selected with --sens-method / --sens-scheme / --sens-step. NC is one of\n"
9485 "the two engines whose exact branch differentiates the product-form\n"
9486 "recursion analytically, so auto takes it wherever the model is in its\n"
9487 "scope (single-server queues plus delays, not mixed) and falls back to\n"
9488 "finite differences elsewhere. NOT the -s ctmc -a sens analysis, which\n"
9489 "is getSensitivityRanking, a ranking of rate perturbations and not a\n"
9490 "table of derivatives.\n"
9491 "-a node is @NetworkSolver's getAvgNodeTable and is served by mva, nc,\n"
9492 "mam, ba and ctmc, the model solvers whose runner returns the station\n"
9493 "AvgResult it is built from. It is a DIFFERENT INDEX SPACE from -a avg,\n"
9494 "not a relabelling: the AvgTable has one row per STATION, so a\n"
9495 "ClassSwitch, Router, Fork, Join or Sink never appears in it, yet jobs\n"
9496 "flow through all of them. QLen, Util, RespT and ResidT are the station\n"
9497 "numbers scattered to their node indices and zero elsewhere -- a node\n"
9498 "that is not a station holds no jobs -- while ArvR and Tput are\n"
9499 "recomputed for every node by sn_get_node_arvr_from_tput and\n"
9500 "sn_get_node_tput_from_tput. The reference's finite-capacity-region\n"
9501 "pseudo-node rows are NOT emitted: this port's AvgResult carries no\n"
9502 "per-region queue length or utilization to fill them with.\n"
9504 "auto picks the engine\n"
9505 "with chooseSolverHeur and prints the name it picked; a branch selecting\n"
9506 "JMT or LDES refuses by name rather than substituting another. ctmc reads\n"
9507 "--cutoff and ports -a avg, prob, gen, states, tranprob, sample, reward,\n"
9508 "cdf, first-passt and sens, which are @SolverCTMC's\n"
9509 "getProbSys/getProbSysAggr and the per-station getProb/getProbAggr,\n"
9510 "getInfGen, getStateSpace, getTranProbSysAggr, sampleSys, getAvgReward,\n"
9511 "getCdfRespT, getCdfFirstPassT (state sets via --passage-from and\n"
9512 "--passage-into, as 1-based space rows '3,5' or state rows '0,2;1,1'),\n"
9513 "first-passt-moments (-a firstpasstmom, the same two sets plus\n"
9514 "--passage-orders: exact moments by one linear solve per order, so a\n"
9515 "variance or a skewness costs no truncated curve)\n"
9516 "and getSensitivityRanking. Its --method also takes mdd, which holds the\n"
9517 "reachable set in a decision diagram and solves K coupled level-CTMCs\n"
9518 "instead of the |S|-state generator, and cftp / cftp.approx, which draw\n"
9519 "iid states from the exact stationary law by coupling from the past; all\n"
9520 "three serve -a avg only, having no explicit chain to answer the rest\n"
9521 "from, and the cftp rows carry Monte Carlo error. mam ports -a avg,\n"
9522 "prob, cdf, tran and internals, which are @SolverMAM's getProb and\n"
9523 "getProbMarg (the joint (level, phase) law of the queue and its\n"
9524 "per-class marginals, truncated at --cutoff when the model is open),\n"
9525 "getCdfRespT with getPerctRespT beside it, getTranAvg over --tspan (the\n"
9526 "reference forces method ldqbd there, so --method does not select it),\n"
9527 "and getMAMResult, the M/G/1-type internals of a single queue. fluid\n"
9528 "additionally reads -a tran, -a prob, -a cdf, -a var and -a aoi, which\n"
9529 "are @SolverFLD's getTranAvg (the metrics along the trajectory, over\n"
9530 "--tspan or, without one, the horizon the reference's own adaptive loop\n"
9531 "converges at), getProbAggr (a law FITTED to the fluid means, so it does\n"
9532 "not agree with the mva or nc answer by construction), getCdfRespT (the\n"
9533 "response-time law per station and class, read off a marked-fluid\n"
9534 "integration started from the steady state), getMoments/getTranAvgVar\n"
9535 "and getAvgAoI with getCdfAoI beside it -- the last needing method mfq\n"
9536 "and the Source/Queue/Sink topology the age laws are defined for. It\n"
9537 "further reads -a odes, which is\n"
9538 "@SolverFLD/exportODEs, and --notation for the form it writes, and -a\n"
9539 "jacobian, which is @SolverFLD/getJacobian: d f_i / d x_j of the drift,\n"
9540 "differentiated locally over the structure of the system, with the\n"
9541 "equilibria beside it under --equilibria, which needs the\n"
9542 "line-sage-rest backend --symbolic names. Only the smooth methods have\n"
9543 "a Jacobian: min(n,S) has none where the regime switches, so the\n"
9544 "min-scaled drifts are refused by the factor that carries the kink.\n"
9545 "nc, ba and ctmc run\n"
9546 "under every --arith backend, ctmc's cftp method excepted: its sampler\n"
9547 "works in the log domain, so it refuses 'exact' by name rather than\n"
9548 "answering in a field it does not live in. mdd does run under 'exact'\n"
9549 "(its level solve drops Householder for a rational least squares there),\n"
9550 "but the LEVEL AGGREGATION is still an approximation away from product\n"
9551 "form: exact arithmetic pins the fixed point, not the model. mam is\n"
9552 "double only (its phase-type fitter\n"
9553 "needs transcendental arithmetic) and so is ssa (its sample path is\n"
9554 "generated from exponential clocks) and fluid (LSODA). ba reports a\n"
9555 "BOUND, not an estimate, and ssa a simulation carrying Monte Carlo\n"
9556 "error; neither is comparable with an exact solver except as such.\n"
9558 "Arithmetic: 'exact' computes in arbitrary-precision rationals and\n"
9559 "reports numerator and denominator alongside the double value; 'real'\n"
9560 "computes in fixed high-precision binary floating point, at 50, 100 or\n"
9561 "200 digits (a request in between is rounded up to the next tier).\n"
9563 "--api arguments are a JSON object keyed by the MATLAB parameter names,\n"
9564 "e.g. {\"L\": [[0.6,0.4]], \"N\": [2,1], \"Z\": [1,0.5]}: a 2-D array is a\n"
9565 "matrix (row-major), a 1-D array a row vector, a bare number a scalar.\n"
9566 "A JSON number is read as its shortest round-tripping decimal, so 0.6 is\n"
9567 "3/5 in exact arithmetic; pass a string such as \"1/3\" for anything else.\n",
9584bool install_check() {
9585 bool has_warnings =
false;
9590 const auto warn = [&](
const std::string& text) {
9591 std::fflush(stdout);
9592 std::fprintf(stderr,
"%s\n", text.c_str());
9593 std::fflush(stderr);
9594 has_warnings =
true;
9597 std::printf(
"Checking LINE (C++)...\n");
9598 std::printf(
" line-cli %s\n", kVersion);
9600 std::printf(
"Checking Java runtime (JMT wrapper)...\n");
9601 const std::string java = line::jmt::detail::find_java();
9603 warn(
"WARNING: no Java runtime was found in LINE_JAVA, JAVA_HOME or on PATH, so the "
9604 "JMT wrapper (-s jmt) cannot run. Install a JRE 8 or later.");
9606 std::printf(
" %s\n", java.c_str());
9609 std::printf(
"Checking JMT...\n");
9610 const std::string jmt_dir = line::jmt::detail::jmt_jar_path();
9611 if (jmt_dir.empty()) {
9612 warn(
"WARNING: JMT.jar was not found, this is required by the JMT wrapper. Download it "
9613 "from https://line-solver.sourceforge.net/latest/JMT.jar into common/, or point "
9614 "LINE_JMT_DIR at the folder holding it.");
9616 std::printf(
" %s/JMT.jar\n", jmt_dir.c_str());
9619 std::printf(
"Checking LQNS...\n");
9623 warn(
"WARNING: lqns is not installed, this is required by the LQNS wrapper (-s lqns) "
9624 "for layered models. Download it at: https://github.com/layeredqueuing/dist");
9626 warn(
"WARNING: the installed lqns is too old for LINE, which needs release 6 or "
9627 "later; it reports '" + banner +
"'. Upgrade it from: "
9628 "https://github.com/layeredqueuing/dist");
9633 std::printf(
"Checking QNS (qnsolver)...\n");
9635 warn(
"WARNING: qnsolver is not installed, this is required by the QNS wrapper (-s qns). "
9636 "It ships with LQNS: https://github.com/layeredqueuing/dist");
9638 std::printf(
"Checking symbolic backend (line-sage-rest)...\n");
9640 warn(std::string(
"WARNING: Docker is not available, so the SageMath symbolic backend "
9641 "cannot start. It is the only computer algebra system this edition "
9642 "reaches and is required by the symbolic methods of "
9643 "SolverCTMC/SolverFluid. Install Docker, then run: "
9646 warn(std::string(
"WARNING: the line-sage-rest image is not present locally, this may be "
9647 "required by some LINE methods. Pull it with: "
9651 std::printf(
"Completed. LINE has warnings.\n");
9653 std::printf(
"Success. LINE is ready to use.\n");
9654 return !has_warnings;
9659 std::printf(
"%-24s %-8s %-24s %s\n",
"function",
"domain",
"arithmetic",
"ported from");
9660 for (
const auto& e :
reg) {
9662 for (std::size_t k = 0; k < e.arith.size(); ++k) {
9663 if (k) modes +=
",";
9666 std::printf(
"%-24s %-8s %-24s %s\n", e.name.c_str(), e.domain.c_str(), modes.c_str(),
9667 e.reference.c_str());
9669 std::printf(
"\n%zu of ~480 API functions ported.\n",
reg.size());
9681 source =
"standard input";
9682 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
9684 source =
"'" + path +
"'";
9685 std::ifstream in(path.c_str());
9687 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
9690 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
9691 throw line::InputError(
"no --api arguments given: pass --args <path> or a JSON object on "
9692 "standard input (read from " +
9695 return line::reg::Json::parse(text);
9696 }
catch (
const line::reg::Json::parse_error& e) {
9697 throw line::InputError(
"malformed --api arguments in " + source +
": " + e.what());
9718std::string normalize_analysis(
const std::string& a) {
9720 if (a ==
"cdf-respt" || a ==
"cdfrespt")
return "cdf";
9721 if (a ==
"cdf-passt" || a ==
"cdfpasst")
return "cdfpasst";
9722 if (a ==
"first-passt" || a ==
"cdf-firstpasst" || a ==
"cdffirstpasst")
return "firstpasst";
9723 if (a ==
"first-passt-moments" || a ==
"firstpasst-moments" || a ==
"firstpasstmoments")
9724 return "firstpasstmom";
9725 if (a ==
"perct-respt" || a ==
"perctrespt")
return "perct";
9726 if (a ==
"tran-avg" || a ==
"tranavg")
return "tran";
9727 if (a ==
"tran-cdf-respt" || a ==
"trancdfrespt")
return "trancdf";
9728 if (a ==
"tran-cdf-passt" || a ==
"trancdfpasst")
return "trancdfpasst";
9729 if (a ==
"tran-prob" || a ==
"tranprob-sys-aggr")
return "tranprob";
9730 if (a ==
"generator")
return "gen";
9731 if (a ==
"state-space" || a ==
"statespace")
return "states";
9732 if (a ==
"reward-steady" || a ==
"rewardsteady")
return "reward";
9733 if (a ==
"reward-value" || a ==
"rewardvalue")
return "rewardvalue";
9734 if (a ==
"node-chain" || a ==
"node-chain-table")
return "nodechain";
9739 if (a ==
"prob-aggr" || a ==
"prob-sys" || a ==
"prob-sys-aggr")
return "prob";
9740 if (a ==
"prob-marg" || a ==
"probmarg")
return "marg";
9741 if (a ==
"prob-sys-marg" || a ==
"probsysmarg" || a ==
"sys-marg")
return "sysmarg";
9742 if (a ==
"sample-aggr" || a ==
"sample-sys" || a ==
"sample-sys-aggr")
return "sample";
9748 if (a ==
"stage")
return "avg";
9753std::vector<std::string> analysis_list(
const std::string& spec) {
9754 std::vector<std::string> out;
9756 while (at <= spec.size()) {
9757 const std::size_t comma = spec.find(
',', at);
9759 spec.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
9761 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.front())))
9762 tok.erase(tok.begin());
9763 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.back())))
9766 throw line::InputError(
"-a takes a comma-separated list of analyses and one entry of '" +
9767 spec +
"' is empty");
9771 if (normalize_analysis(tok) ==
"all") {
9772 out.push_back(
"avg");
9773 out.push_back(
"sys");
9775 out.push_back(normalize_analysis(tok));
9777 if (comma == std::string::npos)
break;
9785 std::string file, input =
"json", output =
"readable", solver =
"auto", analysis =
"avg";
9786 std::string arith =
"double", api, args;
9787 bool help =
false, help_all =
false, version =
false, list =
false;
9797 bool find_solver =
false, find_solver_all =
false;
9798 std::string find_solver_metric;
9800 bool install =
false;
9809 bool input_given =
false;
9824bool has_jsim_extension(
const std::string& file) {
9825 const std::string::size_type
dot = file.find_last_of(
'.');
9826 if (dot == std::string::npos)
return false;
9827 std::string ext = file.substr(dot + 1);
9828 for (std::size_t i = 0; i < ext.size(); ++i)
9829 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
9830 return ext ==
"jsim" || ext ==
"jsimg" || ext ==
"jsimw";
9834bool has_pnml_extension(
const std::string& file) {
9835 const std::string::size_type
dot = file.find_last_of(
'.');
9836 if (dot == std::string::npos)
return false;
9837 std::string ext = file.substr(dot + 1);
9838 for (std::size_t i = 0; i < ext.size(); ++i)
9839 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
9840 return ext ==
"pnml";
9844bool has_lqn_extension(
const std::string& file) {
9845 const std::string::size_type
dot = file.find_last_of(
'.');
9846 if (dot == std::string::npos)
return false;
9847 std::string ext = file.substr(dot + 1);
9848 for (std::size_t i = 0; i < ext.size(); ++i)
9849 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
9850 return ext ==
"lqnx" || ext ==
"xml";
9853Options parse_args(
int argc,
char** argv) {
9855 for (
int i = 1; i < argc; ++i) {
9856 std::string a = argv[i];
9857 auto next = [&](
const char* what) -> std::string {
9858 if (i + 1 >= argc)
throw line::InputError(std::string(
"missing value after ") + what);
9861 if (a ==
"-h" || a ==
"--help") o.help =
true;
9862 else if (a ==
"--help-all" || a ==
"--help-full") o.help_all =
true;
9863 else if (a ==
"-V" || a ==
"--version") o.version =
true;
9864 else if (a ==
"--install") o.install =
true;
9865 else if (a ==
"--list-api") o.list =
true;
9866 else if (a ==
"--find-solver" || a ==
"--find-method" || a ==
"--help-model") {
9867 o.find_solver =
true;
9870 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
9871 }
else if (a ==
"--find-solver-all" || a ==
"--find-method-all") {
9872 o.find_solver =
true;
9873 o.find_solver_all =
true;
9874 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
9876 else if (a ==
"-f" || a ==
"--file") o.file = next(
"-f");
9877 else if (a ==
"-i" || a ==
"--input") { o.input = next(
"-i"); o.input_given =
true; }
9878 else if (a ==
"-o" || a ==
"--output") o.output = next(
"-o");
9879 else if (a ==
"-s" || a ==
"--solver") o.solver = next(
"-s");
9880 else if (a ==
"-a" || a ==
"--analysis") o.analysis = next(
"-a");
9881 else if (a ==
"--arith") o.arith = next(
"--arith");
9882 else if (a ==
"-p" || a ==
"--port") {
9883 const std::string v = next(
"-p");
9884 const long n = std::atol(v.c_str());
9885 if (n < 1 || n > 65535)
9886 throw line::InputError(
"-p takes a TCP port in 1..65535 (got '" + v +
"')");
9887 o.port =
static_cast<int>(n);
9888 }
else if (a ==
"-m" || a ==
"--maxreq") {
9889 const std::string v = next(
"-m");
9890 const long n = std::atol(v.c_str());
9893 "-m is the number of requests the server serves before quitting and must be "
9894 "positive; omit it to serve indefinitely (got '" + v +
"')");
9895 o.maxreq =
static_cast<int>(n);
9897 else if (a ==
"--api") o.api = next(
"--api");
9898 else if (a ==
"--args") o.args = next(
"--args");
9899 else if (a ==
"--method") o.knobs.method = next(
"--method");
9900 else if (a ==
"--qrf-params") o.knobs.qrf_params = next(
"--qrf-params");
9901 else if (a ==
"--qrf-alpha") o.knobs.qrf_alpha = next(
"--qrf-alpha");
9902 else if (a ==
"--level") {
9903 const std::string v = next(
"--level");
9904 const int lv = std::atoi(v.c_str());
9905 if (lv < 1)
throw line::InputError(
"--level must be a positive integer (got '" + v +
"')");
9908 else if (a ==
"--samples") {
9909 const std::string v = next(
"--samples");
9910 const double d = std::atof(v.c_str());
9912 throw line::InputError(
"--samples must be a positive count (got '" + v +
"')");
9913 o.knobs.samples =
static_cast<std::size_t
>(d);
9914 }
else if (a ==
"-d" || a ==
"--seed") {
9915 const std::string v = next(
"--seed");
9916 o.knobs.seed = std::strtoul(v.c_str(),
nullptr, 10);
9917 if (o.knobs.seed == 0)
9918 throw line::InputError(
"--seed must be a positive integer (got '" + v +
"')");
9919 }
else if (a ==
"--warmupfrac") {
9920 const std::string v = next(
"--warmupfrac");
9921 const double f = std::atof(v.c_str());
9922 if (!(f >= 0.0 && f < 1.0))
9924 "--warmupfrac is the fraction of the path discarded before the means are "
9925 "taken and must lie in [0,1) (got '" + v +
"')");
9926 o.knobs.warmupfrac = f;
9927 }
else if (a ==
"--pstar") {
9928 const std::string v = next(
"--pstar");
9929 const double ps = std::atof(v.c_str());
9932 "--pstar is the exponent of the fluid p-norm smoothing and must be positive "
9933 "(got '" + v +
"')");
9935 }
else if (a ==
"--busyperiod" || a ==
"--busyperiod-subnet") {
9938 const bool orders = (a ==
"--busyperiod");
9939 const std::string v = next(orders ?
"--busyperiod" :
"--busyperiod-subnet");
9940 std::vector<std::size_t>& into = orders ? o.knobs.busy_orders : o.knobs.busy_subnet;
9942 while (at <= v.size()) {
9943 const std::size_t comma = v.find(
',', at);
9944 const std::string tok =
9945 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
9946 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos ||
9947 std::atol(tok.c_str()) < 1)
9949 std::string(orders ?
"--busyperiod takes a comma-separated list of "
9951 :
"--busyperiod-subnet takes a comma-separated list of "
9952 "1-based station indexes") +
9953 " (got '" + v +
"')");
9954 into.push_back(
static_cast<std::size_t
>(std::atol(tok.c_str())));
9955 if (comma == std::string::npos)
break;
9958 }
else if (a ==
"--tol") o.knobs.tol = std::atof(next(
"--tol").c_str());
9959 else if (a ==
"--iter_tol") o.knobs.iter_tol = std::atof(next(
"--iter_tol").c_str());
9960 else if (a ==
"--iter_max") o.knobs.iter_max = std::atoi(next(
"--iter_max").c_str());
9961 else if (a ==
"--max-states") {
9962 const std::string v = next(
"--max-states");
9963 const long long n = std::atoll(v.c_str());
9966 "--max-states truncates an open agent's queue-length dimension and takes a "
9967 "positive state count (got '" + v +
"')");
9968 o.knobs.max_states = n;
9970 else if (a ==
"--multiserver") o.knobs.multiserver = next(
"--multiserver");
9971 else if (a ==
"--fork-join" || a ==
"--fork_join")
9972 o.knobs.fork_join = next(
"--fork-join");
9973 else if (a ==
"--tran-points" || a ==
"--tran_points") {
9974 const std::string v = next(
"--tran-points");
9975 const long n = std::atol(v.c_str());
9978 "--tran-points is the number of points on the transient grid and needs at "
9979 "least two, a start and an end (got '" + v +
"')");
9980 o.knobs.tran_points =
static_cast<std::size_t
>(n);
9982 else if (a ==
"--mdd-tol" || a ==
"--mdd_tol") {
9983 const std::string v = next(
"--mdd-tol");
9984 const double d = std::atof(v.c_str());
9986 throw line::InputError(
"--mdd-tol must be a positive tolerance (got '" + v +
"')");
9987 o.knobs.mdd_tol = d;
9988 }
else if (a ==
"--mdd-maxiter" || a ==
"--mdd_maxiter") {
9989 const std::string v = next(
"--mdd-maxiter");
9990 const long n = std::atol(v.c_str());
9993 "--mdd-maxiter must be a positive sweep count (got '" + v +
"')");
9994 o.knobs.mdd_maxiter =
static_cast<int>(n);
9996 else if (a ==
"--fj-accuracy") {
9997 const std::string v = next(
"--fj-accuracy");
9998 const long n = std::atol(v.c_str());
10001 "--fj-accuracy is the FJ_codes truncation C of the queue-length difference "
10002 "between the two fork-join branches and must be at least 1 (got '" + v +
"')");
10003 o.knobs.fj_accuracy =
static_cast<int>(n);
10004 }
else if (a ==
"--fj-tmode") {
10005 const std::string v = next(
"--fj-tmode");
10006 if (v !=
"NARE" && v !=
"Sylves")
10008 "--fj-tmode selects how computeT.m solves for the T matrix and is 'NARE' (the "
10009 "Riccati route, the default) or 'Sylves' (the fixed-point iteration); got '" +
10011 o.knobs.fj_tmode = v;
10012 }
else if (a ==
"--timescale") {
10013 const std::string v = next(
"--timescale");
10014 if (v !=
"auto" && v !=
"discrete" && v !=
"continuous")
10016 "--timescale decides whether the model is read on a slot lattice and is "
10017 "'auto' (the default), 'discrete' or 'continuous'; got '" + v +
"'");
10018 o.knobs.timescale = v;
10020 else if (a ==
"--force") {
10021 o.knobs.force =
true;
10023 else if (a ==
"--cutoff") {
10024 const std::string v = next(
"--cutoff");
10025 if (v.find(
',') != std::string::npos || v.find(
';') != std::string::npos) {
10026 o.knobs.cutoff_mat = parse_cutoff_matrix(v);
10027 if (o.knobs.cutoff_mat.empty())
10029 "--cutoff takes a number or a per-(station,class) matrix written "
10030 "'r1c1,r1c2;r2c1,r2c2' (got '" + v +
"')");
10032 const double d = std::atof(v.c_str());
10035 "--cutoff must be a positive job count per open class (got '" + v +
"')");
10036 o.knobs.cutoff = d;
10038 }
else if (a ==
"--tspan" || a ==
"--timespan") {
10039 const std::string v = next(
"--tspan");
10045 std::string::size_type sep = v.find(
':');
10046 if (sep == std::string::npos) sep = v.find(
',');
10049 const double lo = sep == std::string::npos ? 0.0 : std::atof(v.substr(0, sep).c_str());
10050 const double hi = std::atof(
10051 (sep == std::string::npos ? v : v.substr(sep + 1)).c_str());
10054 if (!(hi > lo) || !(lo >= 0.0) || !std::isfinite(hi))
10056 "--tspan must be a finite horizon 0 <= t0 < t1, given as <t1>, <t0>:<t1> or "
10057 "<t0>,<t1> (got '" + v +
"')");
10060 }
else if (a ==
"-n" || a ==
"--node") {
10061 const std::string v = next(
"--node");
10062 const long n = std::atol(v.c_str());
10064 throw line::InputError(
"--node must be a positive 1-based node index (got '" + v +
10066 o.knobs.node =
static_cast<std::size_t
>(n);
10067 }
else if (a ==
"-c" || a ==
"--class") {
10068 const std::string v = next(
"--class");
10069 const long c = std::atol(v.c_str());
10071 throw line::InputError(
"--class must be a positive 1-based class index (got '" + v +
10073 o.knobs.jobclass =
static_cast<std::size_t
>(c);
10074 }
else if (a ==
"--marg-states" || a ==
"--marg_states") {
10076 const std::string v = next(
"--marg-states");
10077 std::size_t at = 0;
10078 while (at <= v.size()) {
10079 const std::size_t comma = v.find(
',', at);
10080 const std::string tok =
10081 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
10082 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
10084 "--marg-states takes a comma-separated list of non-negative job counts "
10085 "(got '" + v +
"')");
10086 o.knobs.marg_states.push_back(std::atol(tok.c_str()));
10087 if (comma == std::string::npos)
break;
10090 }
else if (a ==
"--state") {
10094 const std::string v = next(
"--state");
10095 std::size_t at = 0;
10096 while (at <= v.size()) {
10097 const std::size_t comma = v.find(
',', at);
10098 const std::string tok =
10099 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
10100 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
10102 "--state is the state vector -a prob asks about and takes a "
10103 "comma-separated list of non-negative counts (got '" + v +
"')");
10104 o.knobs.state.push_back(std::atol(tok.c_str()));
10105 if (comma == std::string::npos)
break;
10108 }
else if (a ==
"--events") {
10109 const std::string v = next(
"--events");
10110 const double d = std::atof(v.c_str());
10113 "--events is the length of a sampled trajectory and must be a positive event "
10114 "count (got '" + v +
"')");
10115 o.knobs.events =
static_cast<std::size_t
>(d);
10116 }
else if (a ==
"--timestep") {
10117 const std::string v = next(
"--timestep");
10118 const double d = std::atof(v.c_str());
10119 if (!(d > 0.0) || !std::isfinite(d))
10121 "--timestep is the fixed output step of a transient analysis and must be a "
10122 "positive finite time (got '" + v +
"')");
10123 o.knobs.timestep = d;
10124 }
else if (a ==
"--percentiles") {
10125 const std::string v = next(
"--percentiles");
10126 std::size_t at = 0;
10127 while (at <= v.size()) {
10128 const std::size_t comma = v.find(
',', at);
10129 const std::string tok =
10130 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
10133 "--percentiles takes a comma-separated list of levels (got '" + v +
"')");
10134 double p = std::atof(tok.c_str());
10141 if (p > 1.0) p /= 100.0;
10142 if (!(p > 0.0) || !(p < 1.0))
10144 "--percentiles levels lie strictly inside (0,1) as fractions or (0,100) "
10145 "as percents; the 100th percentile of an unbounded law is not finite "
10146 "(got '" + tok +
"')");
10147 o.knobs.percentiles.push_back(p);
10148 if (comma == std::string::npos)
break;
10151 }
else if (a ==
"--reward-name" || a ==
"--reward_name") {
10152 o.knobs.reward_name = next(
"--reward-name");
10153 }
else if (a ==
"--notation") {
10154 const std::string v = next(
"--notation");
10157 o.knobs.notation = v;
10158 }
else if (a ==
"--symbolic") {
10162 o.knobs.symbolic = next(
"--symbolic");
10163 }
else if (a ==
"--equilibria") o.knobs.equilibria =
true;
10164 else if (a ==
"--perm-engine") {
10167 o.knobs.method_perm = next(
"--perm-engine");
10168 }
else if (a ==
"--transient-method") {
10171 o.knobs.transient_method = next(
"--transient-method");
10172 }
else if (a ==
"--fau-epsilon") {
10173 const std::string v = next(
"--fau-epsilon");
10174 const double d = std::atof(v.c_str());
10175 if (!(d > 0.0) || !std::isfinite(d))
10177 "--fau-epsilon is the probability mass the transient grid may discard and "
10178 "must be a positive finite number (got '" + v +
"')");
10179 o.knobs.fau_epsilon = d;
10180 }
else if (a ==
"--fau-delta") {
10181 const std::string v = next(
"--fau-delta");
10182 const double d = std::atof(v.c_str());
10183 if (!(d >= 0.0) || !std::isfinite(d))
10185 "--fau-delta is the occupancy below which a state is dropped and must be a "
10186 "nonnegative finite number (got '" + v +
"')");
10187 o.knobs.fau_delta = d;
10188 }
else if (a ==
"--cdf-algorithm") {
10191 o.knobs.cdf_algorithm = next(
"--cdf-algorithm");
10192 }
else if (a ==
"--passage-from") o.knobs.passage_from = next(
"--passage-from");
10193 else if (a ==
"--passage-into") o.knobs.passage_into = next(
"--passage-into");
10194 else if (a ==
"--passage-method") o.knobs.passage_method = next(
"--passage-method");
10195 else if (a ==
"--passage-orders")
10196 o.knobs.passage_orders =
static_cast<std::size_t
>(std::stoul(next(
"--passage-orders")));
10197 else if (a ==
"--no-interlocking") o.knobs.no_interlocking =
true;
10198 else if (a ==
"--layer-solver") o.knobs.layer_solver = next(
"--layer-solver");
10199 else if (a ==
"--stage-solver") o.knobs.stage_solver = next(
"--stage-solver");
10200 else if (a ==
"--ln-transient") o.knobs.ln_transient = next(
"--ln-transient");
10201 else if (a ==
"--ln-transient-channels")
10202 o.knobs.ln_transient_channels = next(
"--ln-transient-channels");
10203 else if (a ==
"--sens-method") o.knobs.sens_method = next(
"--sens-method");
10204 else if (a ==
"--sens-scheme") o.knobs.sens_scheme = next(
"--sens-scheme");
10205 else if (a ==
"--sens-step") {
10206 const std::string v = next(
"--sens-step");
10207 const double h = std::atof(v.c_str());
10208 if (!(h > 0.0) || !(h < 1.0))
10210 "--sens-step is the RELATIVE rate perturbation and must lie in (0,1) (got '" +
10212 o.knobs.sens_step = h;
10214 else if (a ==
"--uq-solver") o.knobs.uq_solver = next(
"--uq-solver");
10215 else if (a ==
"--keep") o.knobs.keep =
true;
10216 else if (a ==
"--verbose") o.knobs.verbose =
true;
10217 else if (a ==
"--remote") o.knobs.remote =
true;
10218 else if (a ==
"--remote-url") {
10221 o.knobs.remote_url = next(
"--remote-url");
10222 o.knobs.remote =
true;
10224 else if (a ==
"--timeout") {
10225 const std::string v = next(
"--timeout");
10226 const long s = std::atol(v.c_str());
10228 throw line::InputError(
"--timeout is a deadline in seconds and must be positive "
10229 "(got '" + v +
"')");
10230 o.knobs.timeout_seconds =
static_cast<int>(s);
10232 else if (a ==
"--repeat") {
10233 const std::string v = next(
"--repeat");
10234 const long n = std::atol(v.c_str());
10236 throw line::InputError(
"--repeat must be a positive run count (got '" + v +
"')");
10237 o.knobs.repeat =
static_cast<int>(n);
10240 else if (a ==
"--ldes-tranfilter") {
10241 const std::string v = next(
"--ldes-tranfilter");
10242 if (v !=
"mser5" && v !=
"fixed" && v !=
"none")
10244 "--ldes-tranfilter selects the warmup filter and is mser5, fixed or none (got '" +
10246 o.knobs.ldes_tranfilter = v;
10248 else if (a ==
"--ldes-warmupfrac") {
10249 const std::string v = next(
"--ldes-warmupfrac");
10250 const double d = std::atof(v.c_str());
10251 if (!(d >= 0.0 && d < 1.0))
10253 "--ldes-warmupfrac is the fraction of the run the fixed filter discards and "
10254 "lies in [0,1) (got '" + v +
"')");
10255 o.knobs.ldes_warmupfrac = d;
10257 else if (a ==
"--ldes-cimethod") {
10258 const std::string v = next(
"--ldes-cimethod");
10259 if (v !=
"obm" && v !=
"bm" && v !=
"spectral" && v !=
"none")
10261 "--ldes-cimethod selects the confidence-interval estimator and is obm, bm, "
10262 "spectral or none (got '" + v +
"')");
10263 o.knobs.ldes_cimethod = v;
10265 else if (a ==
"--ldes-cnvgon") o.knobs.ldes_cnvgon =
true;
10266 else if (a ==
"--ldes-cnvgtol") {
10269 const std::string v = next(
"--ldes-cnvgtol");
10270 const double d = std::atof(v.c_str());
10271 if (!(d > 0.0 && d < 1.0))
10273 "--ldes-cnvgtol is a RELATIVE precision target and lies in (0,1) (got '" + v +
10275 o.knobs.ldes_cnvgtol = d;
10276 o.knobs.ldes_cnvgon =
true;
10278 else if (a ==
"--ldes-slotted") o.knobs.ldes_slotted =
true;
10279 else if (a ==
"--slotted") o.knobs.slotted =
true;
10280 else if (a ==
"--slotlength") {
10282 const std::string v = next(
"--slotlength");
10283 const double d = std::atof(v.c_str());
10286 "--slotlength is the slot of the discrete time scale and must be positive "
10287 "(got '" + v +
"')");
10288 o.knobs.slotlength = d;
10289 o.knobs.slotted =
true;
10291 else if (a ==
"--ldes-slotlength") {
10293 const std::string v = next(
"--ldes-slotlength");
10294 const double d = std::atof(v.c_str());
10297 "--ldes-slotlength is the slot of the discrete time scale and must be positive "
10298 "(got '" + v +
"')");
10299 o.knobs.ldes_slotlength = d;
10300 o.knobs.ldes_slotted =
true;
10302 else if (a ==
"--ldes-replications") {
10303 const std::string v = next(
"--ldes-replications");
10304 const long n = std::atol(v.c_str());
10307 "--ldes-replications is a positive count of independent runs (got '" + v +
"')");
10308 o.knobs.ldes_replications =
static_cast<int>(n);
10310 else if (a ==
"--ldes-numthreads") {
10311 const std::string v = next(
"--ldes-numthreads");
10312 const long n = std::atol(v.c_str());
10315 "--ldes-numthreads is a positive worker count (got '" + v +
"')");
10316 o.knobs.ldes_numthreads =
static_cast<int>(n);
10318 else if (a ==
"--ldes-maxtime") {
10319 const std::string v = next(
"--ldes-maxtime");
10320 const double d = std::atof(v.c_str());
10323 "--ldes-maxtime is a wall-clock budget in seconds and must be positive (got '" +
10325 o.knobs.ldes_maxtime = d;
10327 else if (a ==
"--ldes-initsol") {
10330 const std::string v = next(
"--ldes-initsol");
10332 while (b <= v.size()) {
10333 const std::size_t e = v.find(
',', b);
10334 const std::string tok =
10335 v.substr(b, e == std::string::npos ? std::string::npos : e - b);
10338 "--ldes-initsol is a comma-separated placement with no empty entry (got '" +
10340 o.knobs.ldes_initsol.push_back(std::atof(tok.c_str()));
10341 if (e == std::string::npos)
break;
10345 else if (a ==
"--ldes-rest-url") o.knobs.ldes_rest_url = next(
"--ldes-rest-url");
10346 else if (a ==
"-v" || a ==
"--verbosity") {
10347 const std::string v = next(a.c_str());
10352 if (v !=
"silent" && v !=
"standard" && v !=
"normal" && v !=
"debug" &&
10355 "-v takes silent, standard (the JAR spells it normal) or debug; got '" + v +
10357 o.knobs.verbosity = (v ==
"normal") ?
"standard" : v;
10359 else if (!a.empty() && a[0] ==
'-')
10392 if (o.file.empty())
10393 throw line::InputError(
"--find-solver reports on a model; name one with -f");
10396 "--find-solver reports on a flat Network model; a layered one is solved by -s ln "
10397 "and -s lqns, which this port reaches through the -i lqnx path");
10400 net.
get_struct(), o.find_solver_metric, o.find_solver_all);
10409 if (!o.api.empty()) {
10410 if (o.output !=
"readable" && o.output !=
"json")
10412 "'; accepted forms are: readable, json");
10415 if (o.output ==
"json")
10416 std::printf(
"%s\n", result.dump(2).c_str());
10424 if (!o.input_given && has_lqn_extension(o.file)) o.input =
"lqnx";
10426 if (!o.input_given && has_jsim_extension(o.file)) o.input =
"jsimg";
10428 if (!o.input_given && has_pnml_extension(o.file)) o.input =
"pnml";
10434 if (!o.file.empty() && o.input !=
"lqnx" && o.input !=
"xml" && o.input !=
"pnml" &&
10435 !has_pnml_extension(o.file) &&
10436 !has_jsim_extension(o.file) && o.input.compare(0, 4,
"jsim") != 0 &&
10439 if (o.input ==
"lqnx" || o.input ==
"xml") {
10440 if (o.output !=
"readable" && o.output !=
"json" && o.output !=
"layers")
10442 "'; accepted forms on the layered path are: readable, "
10450 const std::vector<std::string> as = analysis_list(o.analysis);
10451 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
10452 const int rc = solve_lqn_dispatch(o.arith, o.solver, as[i], o.output, o.file,
10454 if (rc != 0)
return rc;
10456 return solve_lqn_dispatch(o.arith, o.solver, as.back(), o.output, o.file, o.knobs);
10463 if (o.input ==
"jsim" || o.input ==
"jsimg" || o.input ==
"jsimw")
10464 g_jsim_input =
true;
10465 else if (o.input ==
"pnml")
10466 g_pnml_input =
true;
10467 else if (o.input !=
"json")
10469 "the model-solving path reads -i json for a Network model, -i jsim|jsimg|jsimw "
10470 "for a JMT simulation document, -i pnml for a place/transition net and "
10471 "-i lqnx|xml for a layered one (got '" + o.input +
"')");
10486 if (o.output !=
"readable" && o.output !=
"json")
10488 "'; accepted forms are: readable, json");
10489 g_json_output = (o.output ==
"json");
10492 const std::vector<std::string> as = analysis_list(o.analysis);
10493 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
10494 const int rc = solve_model_dispatch(o.arith, o.solver, as[i], o.file, o.knobs);
10495 if (rc != 0)
return rc;
10497 return solve_model_dispatch(o.arith, o.solver, as.back(), o.file, o.knobs);
10511 const char* tmpdir = std::getenv(
"TMPDIR");
10512 std::string path = std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-out-XXXXXX";
10513 std::vector<char> buf(path.begin(), path.end());
10514 buf.push_back(
'\0');
10515 const int tfd = ::mkstemp(&buf[0]);
10518 return "line-cli: cannot create a capture file for the response\n";
10520 path.assign(&buf[0]);
10521 std::fflush(stdout);
10522 const int saved = ::dup(1);
10529 err = std::string(
"line-cli: ") + e.what() +
"\n";
10530 }
catch (
const std::exception& e) {
10532 err = std::string(
"line-cli: unexpected failure: ") + e.what() +
"\n";
10534 std::fflush(stdout);
10538 std::ifstream in(path.c_str());
10539 std::string out((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
10541 std::remove(path.c_str());
10544 return err.empty() ? out : out + err;
10560 std::printf(
"--------------------------------------------------------------------\n");
10561 std::printf(
"LINE Solver - Command Line Interface (C++)\n");
10562 std::printf(
"Copyright (c) 2012-2026, QORE Lab, Imperial College London\n");
10563 std::printf(
"Version %s. All rights reserved.\n", kVersion);
10564 std::printf(
"--------------------------------------------------------------------\n");
10565 std::printf(
"Running in server mode on port %d.\n", base.port);
10567 std::printf(
"Quitting after %d request(s).\n", base.maxreq);
10568 std::fflush(stdout);
10571 while (base.maxreq == 0 || served < base.maxreq) {
10572 const bool ok = server.
serve_one([&](
const std::string& msg) -> std::string {
10573 const std::string::size_type nl = msg.find(
'\n');
10574 if (nl == std::string::npos)
10575 return "line-cli: the request's first line is the argument list and its "
10576 "remainder is the model document; this message has no newline\n";
10577 const std::string argline = msg.substr(0, nl);
10578 const std::string model = msg.substr(nl + 1);
10580 const char* tmpdir = std::getenv(
"TMPDIR");
10582 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-req-XXXXXX";
10583 std::vector<char> nb(path.begin(), path.end());
10584 nb.push_back(
'\0');
10585 const int mfd = ::mkstemp(&nb[0]);
10586 if (mfd < 0)
return "line-cli: cannot stage the client model\n";
10588 path.assign(&nb[0]);
10590 std::ofstream mf(path.c_str());
10599 std::vector<std::string> toks;
10600 std::string::size_type at = 0;
10601 while (at <= argline.size()) {
10602 const std::string::size_type comma = argline.find(
',', at);
10603 toks.push_back(argline.substr(
10604 at, comma == std::string::npos ? std::string::npos : comma - at));
10605 if (comma == std::string::npos)
break;
10608 std::string result;
10609 if (toks.size() < 2) {
10610 result =
"line-cli: the argument list needs at least two tokens; the first two "
10611 "are replaced by --file and the staged model path\n";
10613 toks[0] =
"--file";
10615 std::vector<char*> argv;
10616 std::vector<std::string> store;
10617 store.push_back(
"line-cli");
10618 for (std::size_t i = 0; i < toks.size(); ++i) store.push_back(toks[i]);
10619 for (std::size_t i = 0; i < store.size(); ++i)
10620 argv.push_back(
const_cast<char*
>(store[i].c_str()));
10623 Options ro = parse_args(
static_cast<int>(argv.size()), &argv[0]);
10631 result = std::string(
"line-cli: ") + e.what() +
"\n";
10634 std::remove(path.c_str());
10647 Options o = parse_args(argc, argv);
10650 if (!o.knobs.verbosity.empty()) g_verbosity = o.knobs.verbosity;
10657 : (g_verbosity ==
"debug" || g_verbosity ==
"verbose")
10664 if (o.help || argc == 1) {
10665 print_brief_help();
10669 std::printf(
"line-cli %s\n", kVersion);
10687 if (!o.file.empty())
10689 "-p runs the solver as a server, where each request carries its own model; "
10690 "-f names a model on the command line and the two cannot both be the source");
10695 std::fprintf(stderr,
"line-cli: %s\n", e.what());
10697 }
catch (
const std::exception& e) {
10698 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.
Base error for the multiprecision C++ port.
The algorithm cannot proceed on this instance (singular matrix, ...).
Requested feature or arithmetic mode is not ported yet.
const EnvStage< T > & stage(std::size_t e) const
std::size_t nstages() const
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
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::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 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 ...
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,...
Read a JMT .jsim / .jsimg / .jsimw model into a qn::Network.
int main(int argc, char **argv)
The NATIVE LDES engine for LAYERED (LQN) models, the C++ twin of jline/solvers/ldes/handlers/Solver_s...
int run_invocation(Options o)
#define LINE_CLI_TABLE_LADDER(FN)
int find_solver_report(const Options &o)
Everything one invocation does once the arguments are in hand.
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 ....
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.
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_solver_name(AutoSolver s)
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)
AutoToken auto_resolve_token(const std::string &raw)
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...
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).
CtmcCftpSolution< T > solver_ctmc_cftp(const NetworkStruct< T > &sn, const CtmcOptions &opt, const CtmcCftpOptions &cftpopt)
Solve with the cftp / cftp.approx method.
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.
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,...
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...
CtmcTransient< T > solver_ctmc_transient_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, const std::vector< T > &grid=std::vector< T >())
Port of solver_ctmc_transient_analyzer.m.
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.
CtmcFirstPassage ctmc_cdf_firstpasst(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, const std::string &method="expm")
Port of @@SolverCTMC/getCdfFirstPassT.m: the distribution of the FIRST PASSAGE TIME from state set A ...
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.
CtmcFirstPassageMoments< T > ctmc_firstpasst_moments(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, std::size_t nmax=3)
Port of @@SolverCTMC/getFirstPassTMoments.m: moments of order 1..nmax of the first passage time from ...
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 at the runner's own method resolution, so the exported system is the...
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 ...
Response get(const std::string &url, int timeoutMillis)
GET a URL.
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::LqnStruct< T > read_layered_model(const std::string &path)
Read a layered model from either interchange: the LINE model.json or the LQNS .lqnx.
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.
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.
std::vector< std::string > jmt_list_valid_methods()
Port of SolverJMT.listValidMethods.
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),...
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),...
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.
LqnModel< T > read_lqnx_model(const std::string &path)
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.
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.
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.
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)
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,...
mva::AvgResult< T > solver_nc_run_analyzer(const qn::NetworkStruct< T > &L_in, const NcSolverOptions &opt_in)
Port of @@SolverNC/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@Ne...
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.
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.
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.
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.
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.
mva::AvgResult< T > solver_qns_run_analyzer(const qn::NetworkStruct< T > &L, const QnsOptions &opt)
Port of @@SolverQNS/runAnalyzer.m and solver_qns.m.
bool is_available()
Port of SolverQNS.isAvailable: a native qnsolver binary on the PATH.
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.
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.
std::vector< std::string > chain_class_labels(const qn::NetworkStruct< T > &sn)
(ClassA ClassB), the JobClasses column: which classes a chain holds.
SysResult< T > solver_get_avg_sys(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgSys.m.
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)
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.
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.
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....
SsaProbReport solver_ssa_prob(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt)
The whole -a prob report over the model's DEFAULT INITIAL STATE, which is the state SolverCTMC's own ...
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.
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)
solver_ssa_analyzer.m: choose the method.
qn::NetworkStruct< T > sn_with_ssa_cache_split(const qn::NetworkStruct< T > &base, const std::vector< SsaCacheRatio > &cache)
The struct with the cache split the SIMULATION MEASURED, visits rebuilt.
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.
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...
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 cftp and cftp.approx methods of SolverCTMC: stationary analysis of a closed single-class product-...
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 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.
Port of @@SolverQNS, the wrapper around qnsolver of the RADS/LQNS distribution.
Performance sensitivities with respect to service rates.
The SolverSSA queries that are not the average table: getProb, getProbAggr, getProbSys,...
The SolverSSA entry surface: a port of @@SolverSSA/runAnalyzer.m's method whitelist,...
std::string method
'default', 'inap', 'inapplus', '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.
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 > 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/cbh/sib and the iteration count k of pbk/bjbk.
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...
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 knobs of one perfect-sampling run.
std::size_t samples
Number of iid stationary draws; the reference has no default here.
unsigned long seed
Stream seed, so a row is reproducible within this port.
What one cftp solve produces beside the means.
std::vector< std::vector< int > > distinct_states
The distinct sampled states, aligned with paggr.
std::vector< long > horizon
(samples) per-draw coalescence horizon, or the mixing steps of M_A.
The answer of @@SolverCTMC/getFirstPassTMoments.m.
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< 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...
std::vector< double > F
CDF at t, clamped to [0, 1].
std::vector< double > t
the grid, 1000 points to the horizon
std::vector< std::size_t > target
resolved 0-based rows
std::vector< std::size_t > source
resolved 0-based rows; empty = conditional stationary
std::vector< double > f
density at t
[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 transient_method
options.config.transient_method: "ode" (the default) integrates the forward equation,...
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.
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.
EnvStatevecSolution< T > statevec
Populated on the state-vector path.
bool converged
True on the limit path: a closed form has converged by construction.
int iterations
ZERO ON THE LIMIT PATH, and that is the answer rather than a gap: a limit reads the environment once ...
std::string method
The inter-stage coupling: meanfield is the reference's default.
std::string stage_solver
Which solver runs each FLAT stage: the fluid transient or the enumerated CTMC.
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.
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
Solutions of f(x) = 0, each a variable -> expression map.
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::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::vector< double > xvec
the converged fluid state
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.
The options of one JMT solve, SolverOptions('JMT') restricted to what is read.
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).
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 > DropRateNfcr
(nregions x nclasses) carried and lost rate
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::map< std::string, LdesCacheMetrics > cache_metrics
Per-cache metrics, keyed by the Cache NODE name.
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
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....
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::vector< bool > defined_R
std::vector< bool > defined_Q
bool is_bound
True when the numbers are a BOUND (method = mwba.upper / mwba.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
The options SolverMAM reads.
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::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,...
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
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
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
SolverQNS.defaultOptions plus the two knobs the JMVA document carries.
bool keep
options.keep: leave the scratch directory behind, to inspect what was sent.
std::string multiserver
options.config.multiserver.
int timeout
Seconds before a hung qnsolver is killed; not positive waits forever.
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 retrieval latency, 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
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.
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...
double cutoff
< 0 = the reference's automatic value
The serial analyzer's return: the metric table, the path, and the stream.
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 | discrete | quadrature | montecarlo, MATLAB UQ.listValidMethods.
std::size_t samples
Nodes per continuous Prior, or design points under montecarlo; the reference's options....
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: quadrature or montecarlo.
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.