105const char* kVersion =
"3.0.8";
119bool g_json_output =
false;
134std::vector<std::string>* g_json_sink =
nullptr;
138 explicit InterruptRequested(
const std::string& what) : line::Error(what) {}
141int (*g_interrupt_cb)(
void*) =
nullptr;
142void* g_interrupt_user =
nullptr;
151void poll_interrupt() {
152 if (g_interrupt_cb !=
nullptr && g_interrupt_cb(g_interrupt_user) != 0)
153 throw InterruptRequested(
"the run was interrupted by the host");
163void emit_document(
const std::string& text) {
164 if (g_json_sink !=
nullptr) g_json_sink->push_back(text);
165 std::printf(
"%s\n", text.c_str());
193 if (j.is_object() || j.is_array()) {
197 if (!j.is_number_float())
return;
198 const double v = j.get<
double>();
199 if (std::isinf(v)) j = (v > 0.0 ?
"Infinity" :
"-Infinity");
209 finitize_document(j);
210 return indent < 0 ? j.dump() : j.dump(indent);
235 double iter_tol = -1.0;
243 long long max_states = -1;
248 std::string multiserver;
253 std::string fork_join;
256 std::size_t samples = 0;
257 unsigned long seed = 0;
258 double cutoff = -1.0;
264 std::vector<std::vector<std::size_t>> cutoff_mat;
266 bool has_cutoff()
const {
return cutoff >= 0.0 || !cutoff_mat.empty(); }
278 std::string fj_tmode;
285 std::string timescale;
292 double mdd_tol = -1.0;
303 std::vector<std::size_t> busy_orders;
304 std::vector<std::size_t> busy_subnet;
308 std::string qrf_params;
309 std::string qrf_alpha;
313 double t0 = 0.0, t1 = -1.0;
314 std::size_t node = 0;
321 std::size_t jobclass = 0;
322 std::vector<long> marg_states;
328 double warmupfrac = -1.0;
337 std::string notation;
344 std::string cdf_algorithm;
350 std::string passage_from;
351 std::string passage_into;
353 std::string passage_method;
356 std::size_t passage_orders = 0;
366 std::string method_perm =
"exact";
374 std::string symbolic;
375 bool equilibria =
false;
379 bool no_interlocking =
false;
382 std::string interlock_method;
383 double interlock_maxpaths = -1.0;
384 std::string interlock_refpath_scope;
385 bool interlock_knobs_given()
const {
386 return !interlock_method.empty() || interlock_maxpaths >= 0.0 ||
387 !interlock_refpath_scope.empty();
390 std::string layer_solver;
400 std::string stage_solver;
410 std::string map_env_method;
411 std::size_t map_env_maxstages = 0;
414 std::string ln_transient;
415 std::string ln_transient_channels;
420 std::string sens_method;
421 std::string sens_scheme;
422 double sens_step = -1.0;
426 std::string uq_solver;
432 std::size_t tran_points = 0;
439 bool verbose =
false;
441 std::string remote_url;
442 int timeout_seconds = 0;
451 std::string ldes_tranfilter;
452 double ldes_warmupfrac = -1.0;
453 std::string ldes_cimethod;
454 bool ldes_cnvgon =
false;
455 double ldes_cnvgtol = -1.0;
456 bool ldes_slotted =
false;
457 double ldes_slotlength = -1.0;
464 bool slotted =
false;
465 double slotlength = -1.0;
466 int ldes_replications = 0;
467 int ldes_numthreads = 0;
468 double ldes_maxtime = -1.0;
469 std::vector<double> ldes_initsol;
470 std::string ldes_rest_url;
475 int jmt_replications = 0;
488 std::vector<long> state;
497 std::size_t events = 0;
505 double timestep = -1.0;
517 std::string transient_method;
524 double fau_epsilon = -1.0;
525 double fau_delta = -1.0;
532 std::string rate_sched;
534 std::size_t ctmc_tv_ngrid = 0;
541 std::vector<double> percentiles;
551 std::string verbosity;
559 std::string reward_name;
572 if (!k.map_env.empty()) c.
mode = k.map_env;
573 if (!k.map_env_method.empty()) c.
method = k.map_env_method;
574 if (k.map_env_maxstages) c.
max_stages = k.map_env_maxstages;
587std::vector<std::vector<std::size_t>> parse_cutoff_matrix(
const std::string& s) {
588 std::vector<std::vector<std::size_t>> out;
589 std::string::size_type pos = 0;
590 while (pos <= s.size()) {
591 const std::string::size_type semi = s.find(
';', pos);
592 const std::string row = s.substr(pos, semi == std::string::npos ? std::string::npos
594 std::vector<std::size_t> cells;
595 std::string::size_type cp = 0;
596 while (cp <= row.size()) {
597 const std::string::size_type comma = row.find(
',', cp);
598 const std::string cell = row.substr(cp, comma == std::string::npos ? std::string::npos
600 if (cell.empty())
return std::vector<std::vector<std::size_t>>();
601 for (std::string::size_type i = 0; i < cell.size(); ++i)
602 if (!std::isdigit(
static_cast<unsigned char>(cell[i])))
603 return std::vector<std::vector<std::size_t>>();
604 cells.push_back(
static_cast<std::size_t
>(std::atol(cell.c_str())));
605 if (comma == std::string::npos)
break;
608 if (cells.empty())
return std::vector<std::vector<std::size_t>>();
609 if (!out.empty() && cells.size() != out[0].size())
610 return std::vector<std::vector<std::size_t>>();
611 out.push_back(cells);
612 if (semi == std::string::npos)
break;
627std::string g_stdin_buf;
628bool g_stdin_loaded =
false;
637void set_stdin_override(
const std::string& text) {
639 g_stdin_loaded =
true;
642const std::string& stdin_model_text() {
643 if (!g_stdin_loaded) {
644 g_stdin_buf.assign(std::istreambuf_iterator<char>(std::cin),
645 std::istreambuf_iterator<char>());
646 g_stdin_loaded =
true;
659bool g_jsim_input =
false;
669bool g_pnml_input =
false;
678std::string g_verbosity =
"standard";
694void reset_invocation_state(
bool keep_stdin_override) {
695 g_json_output =
false;
696 g_jsim_input =
false;
697 g_pnml_input =
false;
698 g_verbosity =
"standard";
699 if (!keep_stdin_override) {
701 g_stdin_loaded =
false;
722 if (g_verbosity ==
"silent")
return;
726 if (!net.
raw_struct().priorities_ignored())
return;
728 "Warning: Priority classes are specified but no priority-aware scheduling "
729 "policy (PSPRIO, DPSPRIO, GPSPRIO, HOL, FCFSPRIO, FCFSPRPRIO, FCFSPIPRIO, "
730 "LCFSPRIO, LCFSPRPRIO, LCFSPIPRIO, SRPTPRIO) is used in the model. "
731 "Priorities will be ignored.\n");
742 warn_priorities_ignored(net);
747 std::remove(tmp.c_str());
748 warn_priorities_ignored(net);
758 warn_priorities_ignored(net);
763 std::remove(tmp.c_str());
764 warn_priorities_ignored(net);
769 warn_priorities_ignored(net);
772 std::istringstream in(stdin_model_text());
773 line::io::detail::json root;
776 warn_priorities_ignored(net);
784 std::istringstream in(stdin_model_text());
785 line::io::detail::json root;
792 double q, u, r, w, a, t;
824void emit_avg_table_named(
const std::vector<std::string>& stations,
825 const std::vector<std::string>& classes,
const char* arith,
829 for (
const char* key : {
"Station",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
831 rows[key] = line::reg::Json::array();
832 rows[
"type"] =
"AvgTable";
835 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen",
836 "Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
837 for (std::size_t i = 0; i < stations.size(); ++i) {
838 for (std::size_t c = 0; c < classes.size(); ++c) {
839 const AvgRow v =
get(i, c);
840 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)
842 if (!g_json_output) {
843 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
844 stations[i].c_str(), classes[c].c_str(), v.q, v.u, v.r, v.w, v.a, v.t);
851 rows[
"Station"].push_back(stations[i]);
852 rows[
"JobClass"].push_back(classes[c]);
853 rows[
"QLen"].push_back(v.q);
854 rows[
"Util"].push_back(v.u);
855 rows[
"RespT"].push_back(v.r);
856 rows[
"ResidT"].push_back(v.w);
857 rows[
"ArvR"].push_back(v.a);
858 rows[
"Tput"].push_back(v.t);
865 for (line::reg::Json::const_iterator it = extra.begin(); it != extra.end(); ++it)
866 rows[it.key()] = it.value();
869 out[
"arith"] = arith;
870 out[
"method"] = method;
875 for (line::reg::Json::const_iterator it = envelope.begin(); it != envelope.end(); ++it)
876 out[it.key()] = it.value();
877 emit_document(dump_document(out));
890template <
class T,
class Get>
894 std::vector<std::string> stations, classes;
897 for (std::size_t i = 0; i < sn.
nstations; ++i) stations.push_back(sn.
stations[i].name);
898 for (std::size_t c = 0; c < sn.
nclasses; ++c) classes.push_back(sn.
classes[c].name);
911double cache_at(
const std::vector<T>& v, std::size_t r) {
912 if (r >= v.size())
return std::numeric_limits<double>::quiet_NaN();
932 if (cache.
empty())
return;
933 const double dnan = std::numeric_limits<double>::quiet_NaN();
934 std::printf(
"\n%-12s %-12s %5s %12s %12s %12s\n",
"Cache",
"JobClass",
"List",
"HitProb",
935 "DelayedHitP",
"MissProb");
936 for (std::size_t c = 0; c < cache.
caches.size(); ++c) {
938 const typename std::map<std::size_t, line::qn::CacheParam<T> >::const_iterator it =
941 const std::vector<std::size_t>& hitclass = it->second.hitclass;
942 for (std::size_t cl = 0; cl < sn.
nclasses; ++cl) {
946 if (cl >= hitclass.size() || hitclass[cl] == 0)
continue;
947 const double ph = cache_at(m.
hitprob, cl), pm = cache_at(m.
missprob, cl),
949 std::printf(
"%-12s %-12s %5d %12.6g %12.6g %12.6g\n", m.
name.c_str(),
950 sn.
classes[cl].name.c_str(), 0, ph, pd, pm);
952 std::printf(
"%-12s %-12s %5d %12.6g %12.6g %12.6g\n", m.
name.c_str(),
953 sn.
classes[cl].name.c_str(),
static_cast<int>(l + 1),
981void emit_analysis(
const char* key,
const line::reg::Json& payload,
const std::string& method,
990 for (line::reg::Json::const_iterator it = extra.begin(); it != extra.end(); ++it)
991 body[it.key()] = it.value();
995 if (!method.empty()) out[
"method"] = method;
996 emit_document(dump_document(out));
1003 for (std::size_t i = 0; i < M.
rows(); ++i) {
1005 for (std::size_t j = 0; j < M.
cols(); ++j)
1007 rows.push_back(row);
1023 for (std::size_t i = 0; i < v.size(); ++i) a.push_back(v[i]);
1048 for (std::size_t c = 0; c < cache.
caches.size(); ++c) {
1073 caches.push_back(e);
1106 std::fprintf(stderr,
"Warning: %s\n", r.
warning.c_str());
1114 for (std::size_t j = 0; j < r.
listcost.size(); ++j)
1116 extra[
"ListCost"] = lc;
1124 if (extra_in.is_object())
1125 for (line::reg::Json::const_iterator it = extra_in.begin(); it != extra_in.end(); ++it)
1126 extra[it.key()] = it.value();
1130 if (!r.
cache.empty()) extra[
"Cache"] = cache_extra_json<T>(r.
cache);
1137 envelope[
"iter"] = r.
iter;
1144 emit_avg_table<T>(sn, r.
actualmethod, [&](std::size_t i, std::size_t c) {
1146 row.q = line::num_traits<T>::to_double(r.QN(i, c));
1147 row.u = line::num_traits<T>::to_double(r.UN(i, c));
1148 row.r = line::num_traits<T>::to_double(r.RN(i, c));
1149 row.w = line::num_traits<T>::to_double(r.WN(i, c));
1150 row.a = line::num_traits<T>::to_double(r.AN(i, c));
1151 row.t = line::num_traits<T>::to_double(r.TN(i, c));
1153 }, extra, envelope);
1157int solve_model_mva(
const std::string& file,
const Knobs& k) {
1160 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1161 if (k.tol >= 0.0) opt.
tol = k.tol;
1162 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
1163 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1164 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1165 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
1172 if constexpr (std::is_same_v<T, double>) {
1190 print_avg_table<T>(sn, r);
1215 if (g_json_output) {
1217 p[
"type"] =
"TranAvgTable";
1219 p[
"t0"] = k.t0 >= 0.0 ? k.t0 : 0.0;
1224 p[
"replications"] = r.
valid;
1225 p[
"replicationsRequested"] = reps;
1227 for (std::size_t i = 0; i < M && i < r.
QNt.size(); ++i)
1228 for (std::size_t c = 0; c < K && c < r.
QNt[i].size(); ++c) {
1235 e[
"Station"] = sn.
stations[i].name;
1236 e[
"JobClass"] = sn.
classes[c].name;
1239 line::reg::Json tt = line::reg::Json::array(), q = line::reg::Json::array(),
1240 u = line::reg::Json::array(), x = line::reg::Json::array();
1241 for (std::size_t j = 0; j < nt; ++j) {
1242 tt.push_back(r.
t[j]);
1243 q.push_back(r.
QNt[i][c][j]);
1244 u.push_back(r.
UNt[i][c][j]);
1245 x.push_back(r.
TNt[i][c][j]);
1251 curves.push_back(e);
1253 p[
"curves"] = curves;
1257 emit_analysis<double>(
"tran", p, std::string(
"jsim"));
1260 std::printf(
"SolverJMT arith=double method=jsim\n");
1261 std::printf(
"TranAvg times=%zu replications=%zu/%zu\n", nt, r.
valid, reps);
1262 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
1264 for (std::size_t i = 0; i < M && i < r.
QNt.size(); ++i)
1265 for (std::size_t c = 0; c < K && c < r.
QNt[i].size(); ++c) {
1267 for (std::size_t j = 0; j < nt; ++j)
1268 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
1270 r.
QNt[i][c][j], r.
UNt[i][c][j], r.
TNt[i][c][j]);
1290 std::size_t station = 1;
1296 if (k.node > sn.
nodes.size() || sn.
nodes[k.node - 1].station == 0)
1298 "--node " + std::to_string(k.node) +
1299 " is not a station, so it holds no per-class job count to take a law over");
1300 station = sn.
nodes[k.node - 1].station;
1303 std::vector<std::vector<double> > states;
1304 const std::pair<std::vector<double>, std::vector<std::vector<double> > > r =
1306 const std::vector<double>& t = r.first;
1307 const std::vector<std::vector<double> >& pit = r.second;
1309 if (g_json_output) {
1311 p[
"type"] =
"TranProb";
1313 p[
"scope"] = sn.
stations[station - 1].name;
1315 p[
"station"] = station - 1;
1316 p[
"replications"] = reps;
1321 span.push_back(k.t0 >= 0.0 ? k.t0 : 0.0);
1324 p[
"t"] = vector_json(t);
1328 for (std::size_t s = 0; s < states.size(); ++s) labels.push_back(vector_json(states[s]));
1329 p[
"labelsAggr"] = labels;
1331 for (std::size_t g = 0; g < pit.size(); ++g) rows.push_back(vector_json(pit[g]));
1332 p[
"pitAggr"] = rows;
1333 emit_analysis<double>(
"tranprob", p, std::string(
"jsim"));
1336 std::printf(
"SolverJMT arith=double method=jsim\n");
1337 std::printf(
"TranProb times=%zu tspan=%g:%g scope=%s replications=%zu\n", t.size(),
1339 sn.
stations[station - 1].name.c_str(), reps);
1343 std::printf(
"%8s %s\n",
"State",
"Aggregate");
1344 for (std::size_t s = 0; s < states.size(); ++s) {
1345 std::printf(
"%8zu ", s + 1);
1346 for (std::size_t c = 0; c < states[s].size(); ++c) std::printf(
" %g", states[s][c]);
1349 std::printf(
"%14s",
"Time");
1350 for (std::size_t s = 0; s < states.size(); ++s) std::printf(
" %12zu", s + 1);
1352 for (std::size_t g = 0; g < t.size(); ++g) {
1353 std::printf(
"%14.8g", t[g]);
1354 for (std::size_t s = 0; s < states.size(); ++s)
1355 std::printf(
" %12.6g", s < pit[g].size() ? pit[g][s] : 0.0);
1373 const std::size_t nevents = k.events ? k.events : 0;
1377 if (k.node > sn.
nodes.size() || sn.
nodes[k.node - 1].station == 0)
1379 "--node " + std::to_string(k.node) +
1380 " is not a station, so no per-class job count is logged for it");
1383 if (g_json_output) {
1385 p[
"type"] =
"SamplePath";
1387 p[
"scope"] = sn.
nodes[k.node - 1].name;
1388 p[
"node"] = k.node - 1;
1389 p[
"events"] = nevents;
1391 p[
"drawn"] = tr.t.size();
1392 p[
"t"] = vector_json(tr.t);
1394 for (std::size_t j = 0; j < tr.qlen.size(); ++j) st.push_back(vector_json(tr.qlen[j]));
1397 for (std::size_t c = 0; c < K; ++c) cols.push_back(sn.
classes[c].name);
1398 p[
"columns"] = cols;
1399 emit_analysis<double>(
"sample", p, std::string(
"jsim"));
1402 std::printf(
"SolverJMT arith=double method=jsim\n");
1403 std::printf(
"%14s %s\n",
"Time", (
"NodeState (" + sn.
nodes[k.node - 1].name +
1404 ", per class)").c_str());
1405 for (std::size_t j = 0; j < tr.t.size(); ++j) {
1406 std::printf(
"%14.8g ", tr.t[j]);
1407 for (std::size_t c = 0; c < K && c < tr.qlen[j].size(); ++c)
1408 std::printf(
" %g", tr.qlen[j][c]);
1415 const std::size_t M = sn.
nstations, n = tr.t.size();
1416 if (g_json_output) {
1418 p[
"type"] =
"SamplePath";
1420 p[
"scope"] =
"(system)";
1421 p[
"events"] = nevents;
1424 p[
"t"] = vector_json(tr.t);
1426 for (std::size_t j = 0; j < n; ++j) {
1428 for (std::size_t i = 0; i < M; ++i)
1429 for (std::size_t c = 0; c < K; ++c)
1430 row.push_back(i < tr.state.size() && j < tr.state[i].size() &&
1431 c < tr.state[i][j].size()
1438 for (std::size_t i = 0; i < M; ++i)
1439 for (std::size_t c = 0; c < K; ++c)
1441 p[
"columns"] = cols;
1442 emit_analysis<double>(
"sample", p, std::string(
"jsim"));
1445 std::printf(
"SolverJMT arith=double method=jsim\n");
1446 std::printf(
"%14s %s\n",
"Time",
"SysState (station-major, per class)");
1447 for (std::size_t j = 0; j < n; ++j) {
1448 std::printf(
"%14.8g ", tr.t[j]);
1449 for (std::size_t i = 0; i < M; ++i)
1450 for (std::size_t c = 0; c < K; ++c)
1451 std::printf(
" %g", i < tr.state.size() && j < tr.state[i].size() &&
1452 c < tr.state[i][j].size()
1469int solve_model_jmt(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
1474 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
1475 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
1476 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
1481 if (k.jmt_replications > 0) o.
replications = k.jmt_replications;
1483 if (k.jmt_replications > 0 && o.
method ==
"jsim")
1485 "--jmt-replications sizes the transient ensemble of --method default; --method jsim "
1486 "is a single JSIM run, so the count would be ignored rather than honoured");
1489 if (analysis ==
"tran")
return solve_model_jmt_tran(sn, o, k);
1490 if (analysis ==
"tranprob")
return solve_model_jmt_tranprob(sn, o, k);
1491 if (analysis ==
"sample")
return solve_model_jmt_sample(sn, o, k);
1499 if (analysis ==
"prob") {
1500 std::size_t target = 0;
1502 if (k.node <= sn.
nodes.size()) target = sn.
nodes[k.node - 1].station;
1505 "--node " + std::to_string(k.node) +
1506 " is not a station, so it holds no per-class job count to ask about");
1509 sn, o, target, std::vector<double>(k.state.begin(), k.state.end()));
1510 if (g_json_output) {
1512 p[
"type"] =
"ProbAggr";
1514 p[
"ProbSysAggr"] = r.
sys;
1520 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
1521 sv = line::reg::Json::array();
1522 for (std::size_t i = 0; i < sn.
nstations; ++i) {
1529 p[
"StateSeen"] = sv;
1534 emit_analysis<double>(
"prob", p, std::string(
"jsim"));
1537 std::printf(
"SolverJMT arith=double method=jsim\n");
1538 std::printf(
"ProbSysAggr %.10g%s\n", r.
sys, r.
sys_seen ?
"" :
" (state never observed)");
1539 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
1540 for (std::size_t i = 0; i < sn.
nstations; ++i)
1541 std::printf(
"%-16s %14.10g%s\n", sn.
stations[i].name.c_str(), r.
station[i],
1546 if (analysis ==
"cdf" || analysis ==
"trancdf" || analysis ==
"trancdfpasst") {
1550 const std::map<std::pair<std::size_t, std::size_t>,
1551 std::vector<std::pair<double, double> > >
1554 for (std::map<std::pair<std::size_t, std::size_t>,
1555 std::vector<std::pair<double, double> > >::const_iterator it = rd.begin();
1556 it != rd.end(); ++it) {
1558 for (std::size_t i = 0; i < it->second.size(); ++i) {
1560 row.push_back(it->second[i].first);
1561 row.push_back(it->second[i].second);
1562 rows.push_back(row);
1564 const std::string key =
1566 sn.
classes[it->first.second - 1].name;
1569 std::printf(
"%-24s %8zu points respT(max)=%.6g\n", key.c_str(),
1570 it->second.size(), it->second.back().second);
1572 if (g_json_output) {
1575 emit_document(dump_document(out, 2));
1581 std::printf(
"SolverJMT arith=double method=%s\n", r.
avg.actualmethod.c_str());
1590 &table.
TN, &table.
AN, &table.
WN};
1593 for (
int m = 0; m < 6; ++m) {
1595 for (std::size_t i = 0; i < M; ++i)
1596 for (std::size_t c = 0; c < K; ++c) t(i, c) = (*src[m])(i, c);
1609 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array();
1610 line::reg::Json rt = line::reg::Json::array(), w = line::reg::Json::array();
1611 line::reg::Json a = line::reg::Json::array(), t = line::reg::Json::array();
1612 for (std::size_t f = 0; f < sn.
regions.size() && M + f < r.
avg.QN.rows(); ++f) {
1613 names.push_back(f < sn.
regions.size() && !sn.
regions[f].name.empty()
1615 :
"FCR" + std::to_string(f + 1));
1616 for (std::size_t c = 0; c < K; ++c) {
1617 q.push_back(r.
avg.QN(M + f, c));
1618 u.push_back(r.
avg.UN(M + f, c));
1619 rt.push_back(r.
avg.RN(M + f, c));
1620 w.push_back(r.
avg.WN(M + f, c));
1621 a.push_back(r.
avg.AN(M + f, c));
1622 t.push_back(r.
avg.TN(M + f, c));
1625 fcr_extra[
"Region"] = names;
1626 fcr_extra[
"QLen"] = q;
1627 fcr_extra[
"Util"] = u;
1628 fcr_extra[
"RespT"] = rt;
1629 fcr_extra[
"ResidT"] = w;
1630 fcr_extra[
"ArvR"] = a;
1631 fcr_extra[
"Tput"] = t;
1634 if (!fcr_extra.empty()) avg_extra[
"FCR"] = fcr_extra;
1642 if (!r.
QCI.empty()) ci[
"QNCI"] = matrix_json<double>(r.
QCI);
1643 if (!r.
UCI.empty()) ci[
"UNCI"] = matrix_json<double>(r.
UCI);
1644 if (!r.
RCI.empty()) ci[
"RNCI"] = matrix_json<double>(r.
RCI);
1645 if (!r.
TCI.empty()) ci[
"TNCI"] = matrix_json<double>(r.
TCI);
1646 if (!r.
ACI.empty()) ci[
"ANCI"] = matrix_json<double>(r.
ACI);
1647 if (!ci.empty()) avg_extra[
"CI"] = ci;
1648 print_avg_table<double>(sn, table, avg_extra);
1653 if (!g_json_output && !r.
QCI.empty()) {
1654 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLenCI",
1655 "UtilCI",
"RespTCI",
"TputCI",
"ArvRCI");
1656 for (std::size_t i = 0; i < sn.
nstations && i < r.
QCI.rows(); ++i)
1657 for (std::size_t c = 0; c < sn.
nclasses; ++c)
1658 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
1660 r.
UCI.empty() ? 0.0 : r.
UCI(i, c), r.
RCI.empty() ? 0.0 : r.
RCI(i, c),
1661 r.
TCI.empty() ? 0.0 : r.
TCI(i, c),
1662 r.
ACI.empty() ? 0.0 : r.
ACI(i, c));
1665 if (!g_json_output && r.
TNfcr.rows() > 0) {
1666 std::printf(
"%-16s %-14s %12s %12s\n",
"Region",
"JobClass",
"Tput",
"DropRate");
1667 for (std::size_t f = 0; f < r.
TNfcr.rows(); ++f)
1668 for (std::size_t c = 0; c < sn.
nclasses; ++c)
1673 std::printf(
"%-16s %-14s %12.6g %12.6g\n",
1676 :
"FCR" + std::to_string(f + 1))
1681 for (std::map<std::size_t, std::vector<double> >::const_iterator it =
1684 for (std::size_t c = 0; c < it->second.size(); ++c)
1685 std::printf(
"%-16s %-14s hitProb=%12.6g\n", sn.
nodes[it->first - 1].name.c_str(),
1686 sn.
classes[c].name.c_str(), it->second[c]);
1706 "the cftp methods draw random states and form the station balance functions in the "
1707 "log domain, neither of which exists in exact rational arithmetic; rerun with --arith "
1708 "double or --arith real");
1718 double horizon = 0.0;
1719 for (std::size_t i = 0; i < s.
horizon.size(); ++i)
1720 horizon +=
static_cast<double>(s.
horizon[i]);
1721 if (!s.
horizon.empty()) horizon /=
static_cast<double>(s.
horizon.size());
1723 "SolverNC arith=%s method=%s type=%s samples=%zu seed=%lu distinct=%zu "
1724 "meanhorizon=%.6g\n",
1728 print_avg_table<T>(sn, r);
1742int solve_model_nc(
const std::string& file,
const Knobs& k) {
1745 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1746 if (k.tol >= 0.0) opt.
tol = k.tol;
1747 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
1748 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1749 if (k.samples) opt.
samples = k.samples;
1750 if (k.seed) opt.
seed = k.seed;
1751 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
1752 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
1753 if (k.slotted) opt.
slotted =
true;
1754 if (k.slotlength > 0.0) {
1761 if (opt.
method ==
"cftp" || opt.
method ==
"cftp.approx") {
1765 return solve_nc_cftp_avg<T>(net.
get_struct(), opt, cftpopt);
1769 if constexpr (std::is_same_v<T, double>) {
1785 std::printf(
"SolverNC arith=%s method=%s type=%s lognormconst=%.10g\n",
1789 print_avg_table<T>(sn, r);
1803int solve_model_mam(
const std::string& file,
const Knobs& k) {
1806 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1807 if (k.tol >= 0.0) opt.
tol = k.tol;
1808 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1815 print_avg_table<T>(sn, r);
1840 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1841 if (k.tol >= 0.0) opt.
tol = k.tol;
1842 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1843 if (k.max_states > 0) opt.
max_states =
static_cast<std::size_t
>(k.max_states);
1847int solve_model_ag(
const std::string& file,
const Knobs& k) {
1850 apply_ag_knobs(k, opt);
1857 print_avg_table<T>(sn, r);
1872 if (k.node)
return k.node;
1873 for (std::size_t a = 0; a < sn.
nof_nodes(); ++a)
1874 if (sn.
nodes[a].nodetype == line::qn::NodeType::Queue)
return a + 1;
1876 "the MAM per-node analyses report a queue's internals and this model has no Queue node");
1882 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
1883 if (k.tol >= 0.0) opt.
tol = k.tol;
1884 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
1885 if (k.cutoff >= 0.0) opt.
cutoff =
static_cast<std::size_t
>(k.cutoff);
1886 if (k.fj_accuracy > 0) opt.
fj_accuracy =
static_cast<std::size_t
>(k.fj_accuracy);
1887 if (!k.fj_tmode.empty()) opt.
fj_tmode = k.fj_tmode;
1888 if (!k.timescale.empty()) opt.
timescale = k.timescale;
1889 if (k.slotlength > 0.0) opt.
slotlength = k.slotlength;
1911int solve_model_mam_prob(
const std::string& file,
const Knobs& k) {
1918 const std::size_t node = mam_query_node<T>(sn, k);
1919 const std::size_t ist = sn.
nodes[node - 1].station;
1921 std::vector<std::vector<T> > marg(sn.
nclasses);
1922 for (std::size_t r = 0; r < sn.
nclasses; ++r)
1925 if (g_json_output) {
1927 p[
"type"] =
"ProbTable";
1929 p[
"node"] = node - 1;
1930 p[
"Node"] = sn.
nodes[node - 1].name;
1931 p[
"levels"] = P.
P.rows();
1932 p[
"phases"] = P.
P.cols();
1934 for (std::size_t n = 0; n < P.
P.rows(); ++n) {
1936 for (std::size_t j = 0; j < P.
P.cols(); ++j)
1938 joint.push_back(row);
1942 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
1944 e[
"JobClass"] = sn.
classes[r].name;
1946 e[
"P"] = vector_json(marg[r]);
1953 std::printf(
"SolverMAM arith=%s method=%s node=%s levels=%zu phases=%zu\n",
1955 sn.
nodes[node - 1].name.c_str(), P.
P.rows(), P.
P.cols());
1956 std::printf(
"%-8s %-8s %16s\n",
"Level",
"Phase",
"Prob");
1957 for (std::size_t n = 0; n < P.
P.rows(); ++n)
1958 for (std::size_t j = 0; j < P.
P.cols(); ++j)
1959 std::printf(
"%-8zu %-8zu %16.10g\n", n, j + 1,
1961 std::printf(
"%-14s %-8s %16s\n",
"JobClass",
"Jobs",
"Prob");
1962 for (std::size_t r = 0; r < sn.
nclasses; ++r)
1963 for (std::size_t n = 0; n < marg[r].size(); ++n)
1964 std::printf(
"%-14s %-8zu %16.10g\n", sn.
classes[r].name.c_str(), n,
1973std::vector<double> percentile_levels(
const Knobs& k) {
1974 if (!k.percentiles.empty())
return k.percentiles;
1975 std::vector<double> pcts;
1976 pcts.push_back(0.50);
1977 pcts.push_back(0.90);
1978 pcts.push_back(0.95);
1979 pcts.push_back(0.99);
2004int solve_model_mam_cdf(
const std::string& file,
const Knobs& k,
const char* key,
2009 const std::vector<double> pcts = percentile_levels(k);
2012 const std::vector<std::vector<T> > perc =
2014 if (g_json_output) {
2016 p[
"type"] =
"PerctRespT";
2018 p[
"source"] =
"fjcodes";
2020 for (std::size_t r = 0; r < perc.size(); ++r) {
2022 e[
"JobClass"] = sn.
classes[r].name;
2024 e[
"percentileLevels"] = pcts;
2025 e[
"percentiles"] = vector_json(perc[r]);
2029 emit_analysis<T>(key, p, opt.
method);
2034 std::printf(
"# the response-time CDF has no fork-join route in the reference; these are "
2035 "the FJ_codes percentiles getPerctRespT reads\n");
2036 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
2037 for (std::size_t r = 0; r < perc.size(); ++r)
2038 for (std::size_t j = 0; j < perc[r].size(); ++j)
2039 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
2045 std::vector<std::vector<T> > perc;
2046 for (std::size_t r = 0; r < rd.size(); ++r)
2049 if (g_json_output) {
2054 for (std::size_t r = 0; r < rd.size(); ++r) {
2058 if (rd[r].X.empty())
continue;
2060 e[
"JobClass"] = sn.
classes[r].name;
2062 e[
"t"] = vector_json(rd[r].X);
2063 e[
"F"] = vector_json(rd[r].F);
2064 e[
"percentileLevels"] = pcts;
2065 e[
"percentiles"] = vector_json(perc[r]);
2069 emit_analysis<T>(key, p, opt.
method);
2074 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Time",
"F(t)");
2075 for (std::size_t r = 0; r < rd.size(); ++r)
2076 for (std::size_t j = 0; j < rd[r].X.size(); ++j)
2077 std::printf(
"%-14s %14.8g %14.10g\n", sn.
classes[r].name.c_str(),
2080 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
2081 for (std::size_t r = 0; r < perc.size(); ++r)
2082 for (std::size_t j = 0; j < perc[r].size(); ++j)
2083 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
2100int solve_model_mam_perct(
const std::string& file,
const Knobs& k) {
2104 const std::vector<double> pcts = percentile_levels(k);
2106 std::vector<std::vector<T> > perc;
2111 const std::vector<line::mam::RespTCdf<T> > rd =
2113 for (std::size_t r = 0; r < rd.size(); ++r)
2117 if (g_json_output) {
2119 p[
"type"] =
"PerctRespT";
2121 p[
"source"] = fj ?
"fjcodes" :
"cdf";
2123 for (std::size_t r = 0; r < perc.size(); ++r) {
2124 if (perc[r].empty())
continue;
2126 e[
"JobClass"] = sn.
classes[r].name;
2128 e[
"percentileLevels"] = pcts;
2129 e[
"percentiles"] = vector_json(perc[r]);
2133 emit_analysis<T>(
"perct", p, opt.
method);
2136 std::printf(
"SolverMAM arith=%s method=%s classes=%zu source=%s\n",
2138 fj ?
"fjcodes" :
"cdf");
2139 std::printf(
"%-14s %14s %14s\n",
"JobClass",
"Percentile",
"RespT");
2140 for (std::size_t r = 0; r < perc.size(); ++r)
2141 for (std::size_t j = 0; j < perc[r].size(); ++j)
2142 std::printf(
"%-14s %14.4g %14.10g\n", sn.
classes[r].name.c_str(), pcts[j],
2157int solve_model_mam_tran(
const std::string& file,
const Knobs& k) {
2163 if (g_json_output) {
2165 p[
"type"] =
"TranAvgTable";
2170 for (std::size_t i = 0; i < tr.Qt.size(); ++i)
2171 for (std::size_t r = 0; r < tr.Qt[i].size(); ++r) {
2172 if (tr.Qt[i][r].times.empty())
continue;
2174 e[
"Station"] = sn.
stations[i].name;
2175 e[
"JobClass"] = sn.
classes[r].name;
2178 e[
"t"] = tr.Qt[i][r].times;
2179 e[
"QLen"] = vector_json(tr.Qt[i][r].values);
2180 e[
"Util"] = vector_json(tr.Ut[i][r].values);
2181 e[
"Tput"] = vector_json(tr.Tt[i][r].values);
2185 emit_analysis<T>(
"tran", p,
"ldqbd");
2190 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
2192 for (std::size_t i = 0; i < tr.Qt.size(); ++i)
2193 for (std::size_t r = 0; r < tr.Qt[i].size(); ++r)
2194 for (std::size_t j = 0; j < tr.Qt[i][r].times.size(); ++j)
2195 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
2197 tr.Qt[i][r].times[j],
2215int solve_model_mam_internals(
const std::string& file,
const Knobs& k) {
2222 if (g_json_output) {
2224 p[
"type"] =
"MAMResult";
2228 p[
"uniformization"] = q;
2238 p[
"theta"] = vector_json(r.
theta);
2239 p[
"alpha"] = vector_json(r.
alpha);
2241 for (std::size_t n = 0; n < r.
levelProb.rows(); ++n) {
2243 for (std::size_t j = 0; j < r.
levelProb.cols(); ++j)
2247 p[
"levelProb"] = lv;
2248 emit_analysis<T>(
"internals", p, std::string());
2252 std::printf(
"lambda=%.10g rho=%.10g q=%.10g drift=%.10g decayRate=%.10g\n",
2255 std::printf(
"pi0=%.10g QLen=%.10g Util=%.10g Tput=%.10g truncLevel=%zu truncError=%.3g "
2262 std::printf(
"%-8s %16s\n",
"Level",
"Prob");
2263 for (std::size_t n = 0; n < r.
levelProb.rows(); ++n) {
2265 for (std::size_t j = 0; j < r.
levelProb.cols(); ++j)
2267 std::printf(
"%-8zu %16.10g\n", n, s);
2282line::reg::Json read_json_arg(
const std::string& spec,
const char* flag) {
2283 std::string text = spec;
2284 const std::size_t at = spec.find_first_not_of(
" \t\r\n");
2285 if (at == std::string::npos || (spec[at] !=
'{' && spec[at] !=
'[')) {
2286 std::ifstream in(spec.c_str());
2288 throw line::InputError(std::string(flag) +
" is neither inline JSON nor a readable "
2289 "file (got '" + spec +
"')");
2290 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
2293 return line::reg::Json::parse(text);
2294 }
catch (
const line::reg::Json::parse_error& e) {
2295 throw line::InputError(std::string(
"malformed ") + flag +
" JSON: " + e.what());
2300std::vector<std::vector<int> > json_int_table(
const line::reg::Json& j,
const char* name) {
2302 throw line::InputError(std::string(
"--qrf-params ") + name +
" must be an array of rows");
2303 std::vector<std::vector<int> > out;
2304 for (std::size_t m = 0; m < j.size(); ++m) {
2305 if (!j[m].is_array())
2306 throw line::InputError(std::string(
"--qrf-params ") + name +
" must be an array of "
2308 std::vector<int> row;
2309 for (std::size_t c = 0; c < j[m].size(); ++c) row.push_back(j[m][c].get<
int>());
2325 if (!j.is_object())
throw line::InputError(
"--qrf-params must be a JSON object");
2326 const char* required[] = {
"f",
"MR",
"BB",
"MM",
"MM1",
"ZZ"};
2327 std::string missing;
2328 for (std::size_t i = 0; i < 6; ++i)
2329 if (!j.contains(required[i]))
2330 missing += (missing.empty() ?
"" :
", ") + std::string(required[i]);
2331 if (!missing.empty())
2333 "; required are f, MR, BB, MM, MM1, ZZ (F is optional, ZM is "
2334 "derived from ZZ)");
2337 p.
f = j[
"f"].get<
int>();
2338 p.
MR = j[
"MR"].get<
int>();
2339 p.
BB = json_int_table(j[
"BB"],
"BB");
2340 p.
MM = json_int_table(j[
"MM"],
"MM");
2341 p.
MM1 = json_int_table(j[
"MM1"],
"MM1");
2342 if (!j[
"ZZ"].is_array())
throw line::InputError(
"--qrf-params ZZ must be an array");
2343 for (std::size_t i = 0; i < j[
"ZZ"].size(); ++i) p.
ZZ.push_back(j[
"ZZ"][i].get<
int>());
2344 if (j.contains(
"F"))
2345 for (std::size_t i = 0; i < j[
"F"].size(); ++i) p.
F.push_back(j[
"F"][i].get<
int>());
2352 if (!j.is_array() || j.empty() || !j[0].is_array())
2353 throw line::InputError(
"--qrf-alpha must be a JSON array of rows, one per station");
2355 for (std::size_t i = 0; i < j.size(); ++i) {
2356 if (!j[i].is_array() || j[i].size() != j[0].size())
2358 for (std::size_t n = 0; n < j[i].size(); ++n) a(i, n) = j[i][n].get<
double>();
2365 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
2366 if (k.level > 0) opt.
level = k.level;
2367 if (!k.qrf_params.empty()) decode_qrf_params(k.qrf_params, opt);
2368 if (!k.qrf_alpha.empty()) decode_qrf_alpha(k.qrf_alpha, opt);
2372int solve_model_ba(
const std::string& file,
const Knobs& k) {
2375 apply_ba_knobs(k, opt);
2382 print_avg_table<T>(sn, r);
2401int solve_model_ba_bounds(
const std::string& file,
const Knobs& k) {
2404 apply_ba_knobs(k, opt);
2410 if (g_json_output) {
2412 p[
"type"] =
"BoundsTable";
2414 p[
"family"] = am.substr(0, am.find(
'.'));
2417 for (
const char* key : {
"Station",
"JobClass",
"QLower",
"QUpper",
"TLower",
"TUpper"})
2418 p[key] = line::reg::Json::array();
2419 for (std::size_t i = 0; i < sn.
nstations; ++i)
2420 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2421 if (!b.
keep[i][c])
continue;
2422 p[
"Station"].push_back(sn.
stations[i].name);
2423 p[
"JobClass"].push_back(sn.
classes[c].name);
2424 p[
"QLower"].push_back(d(b.
Qlower(i, c)));
2425 p[
"QUpper"].push_back(d(b.
Qupper(i, c)));
2426 p[
"TLower"].push_back(d(b.
Tlower(i, c)));
2427 p[
"TUpper"].push_back(d(b.
Tupper(i, c)));
2429 emit_analysis<T>(
"bounds", p, am);
2432 std::printf(
"SolverBA arith=%s method=%s type=%s family=%s sides=%s\n",
2436 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLower",
"QUpper",
2437 "TLower",
"TUpper");
2438 for (std::size_t i = 0; i < sn.
nstations; ++i)
2439 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2440 if (!b.
keep[i][c])
continue;
2441 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
2457int solve_model_lqns_network(
const std::string& file,
const Knobs& k) {
2460 if (!k.method.empty()) opt.
method = k.method;
2461 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
2466 opt.
timeout = k.timeout_seconds;
2475 print_avg_table<T>(sn, r);
2492 const std::vector<std::vector<line::solvers::DefaultCdfCurve>> RD =
2494 if (g_json_output) {
2496 p[
"type"] =
"CdfRespT";
2499 for (std::size_t i = 0; i < RD.size(); ++i)
2500 for (std::size_t c = 0; c < RD[i].size(); ++c) {
2501 if (RD[i][c].t.empty())
continue;
2503 e[
"Station"] = sn.
stations[i].name;
2504 e[
"JobClass"] = sn.
classes[c].name;
2512 emit_analysis<T>(
"cdf", p, std::string());
2515 std::printf(
"%s arith=%s method=%s (exponential fallback with the solver's mean)\n",
2517 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
2518 for (std::size_t i = 0; i < RD.size(); ++i)
2519 for (std::size_t c = 0; c < RD[i].size(); ++c)
2520 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
2521 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
2522 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].F[j]);
2528int solve_model_mva_cdf(
const std::string& file,
const Knobs& k) {
2531 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
2532 if (k.tol >= 0.0) opt.
tol = k.tol;
2533 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
2534 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
2535 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
2536 if (!k.fork_join.empty()) opt.
fork_join = k.fork_join;
2539 return emit_default_cdf<T>(
"SolverMVA", net.
get_struct(), r);
2544int solve_model_ag_cdf(
const std::string& file,
const Knobs& k) {
2547 apply_ag_knobs(k, opt);
2549 return emit_default_cdf<T>(
"SolverAG", net.
get_struct(), r);
2554int solve_model_ba_cdf(
const std::string& file,
const Knobs& k) {
2557 apply_ba_knobs(k, opt);
2559 return emit_default_cdf<T>(
"SolverBA", net.
get_struct(), r);
2563int solve_model_lqns_network_cdf(
const std::string& file,
const Knobs& k) {
2566 if (!k.method.empty()) opt.
method = k.method;
2567 if (!k.multiserver.empty()) opt.
multiserver = k.multiserver;
2568 opt.
timeout = k.timeout_seconds;
2572 return emit_default_cdf<double>(
"SolverLQNS", net.
get_struct(), r);
2585 const std::string& stage, std::size_t npriors) {
2590 p[
"type"] =
"IntervalTable";
2592 p[
"exact"] = iv.
exact;
2593 p[
"intervalMethod"] = iv.
method;
2594 if (!iv.
why.empty()) p[
"why"] = iv.
why;
2601 for (
const char* key : {
"Station",
"JobClass",
"QLen_lo",
"QLen_up",
"Util_lo",
"Util_up",
2602 "RespT_lo",
"RespT_up",
"Tput_lo",
"Tput_up"})
2603 p[key] = line::reg::Json::array();
2604 if (!g_json_output) {
2605 std::printf(
"SolverUQ arith=%s interval=%s exact=%s priors=%zu stage=%s\n",
2607 npriors, stage.c_str());
2613 std::fprintf(stderr,
2614 "Warning: exact interval MVA does not apply (%s); the range below is "
2615 "over the solved design points and is not an enclosure.\n",
2617 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s %12s %12s\n",
"Station",
2618 "JobClass",
"QLen_lo",
"QLen_up",
"Util_lo",
"Util_up",
"RespT_lo",
2619 "RespT_up",
"Tput_lo",
"Tput_up");
2621 for (std::size_t i = 0; i < sn.
nstations; ++i)
2622 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2625 if (v(iv.
Qup, i, c) <= 0.0 && v(iv.
Uup, i, c) <= 0.0 && v(iv.
Tup, i, c) <= 0.0)
2627 if (!g_json_output) {
2629 "%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
2631 v(iv.
Qup, i, c), v(iv.
Ulo, i, c), v(iv.
Uup, i, c), v(iv.
Rlo, i, c),
2632 v(iv.
Rup, i, c), v(iv.
Tlo, i, c), v(iv.
Tup, i, c));
2635 p[
"Station"].push_back(sn.
stations[i].name);
2636 p[
"JobClass"].push_back(sn.
classes[c].name);
2637 p[
"QLen_lo"].push_back(v(iv.
Qlo, i, c));
2638 p[
"QLen_up"].push_back(v(iv.
Qup, i, c));
2639 p[
"Util_lo"].push_back(v(iv.
Ulo, i, c));
2640 p[
"Util_up"].push_back(v(iv.
Uup, i, c));
2641 p[
"RespT_lo"].push_back(v(iv.
Rlo, i, c));
2642 p[
"RespT_up"].push_back(v(iv.
Rup, i, c));
2643 p[
"Tput_lo"].push_back(v(iv.
Tlo, i, c));
2644 p[
"Tput_up"].push_back(v(iv.
Tup, i, c));
2646 if (g_json_output) emit_analysis<T>(
"interval", p, iv.
method);
2670int solve_model_uq(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
2673 if (!k.method.empty()) opt.
method = k.method;
2674 if (k.samples) opt.
samples = k.samples;
2675 if (k.seed) opt.
seed = k.seed;
2683 if (analysis ==
"interval") {
2698 if (analysis ==
"avg") {
2700 std::printf(
"SolverUQ arith=%s design=%s points=%zu priors=%zu stage=%s method=%s\n",
2703 print_avg_table<T>(sn, r.
avg);
2709 p[
"type"] =
"PosteriorTable";
2714 for (std::size_t l = 0; l < r.
sites.size(); ++l) {
2716 s[
"node"] = sn.
nodes[r.
sites[l].node - 1].name;
2718 s[
"kind"] = r.
sites[l].arrival ?
"arrival" :
"service";
2721 p[
"priors"] = sites;
2722 for (
const char* key : {
"Point",
"Weight",
"Station",
"JobClass",
"QLen",
"Util",
"RespT",
2724 p[key] = line::reg::Json::array();
2727 if (!g_json_output) {
2728 std::printf(
"SolverUQ arith=%s design=%s points=%zu priors=%zu stage=%s\n",
2733 std::printf(
"%-6s %12s substituted means\n",
"Point",
"Weight");
2734 for (std::size_t e = 0; e < r.
points.size(); ++e) {
2736 for (std::size_t l = 0; l < r.
sites.size(); ++l)
2737 std::printf(
" %s@%s=%.6g", sn.
nodes[r.
sites[l].node - 1].name.c_str(),
2742 std::printf(
"%-6s %12s %-16s %-14s %12s %12s %12s %12s\n",
"Point",
"Weight",
"Station",
2743 "JobClass",
"QLen",
"Util",
"RespT",
"Tput");
2745 for (std::size_t e = 0; e < r.
points.size(); ++e) {
2747 for (std::size_t l = 0; l < r.
sites.size(); ++l)
2749 means.push_back(row);
2751 for (std::size_t i = 0; i < sn.
nstations; ++i)
2752 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
2760 if (q <= 0.0 && u <= 0.0 && t <= 0.0)
continue;
2761 if (!g_json_output) {
2762 std::printf(
"%-6zu %12.6g %-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", e + 1,
2768 p[
"Point"].push_back(e);
2770 p[
"Station"].push_back(sn.
stations[i].name);
2771 p[
"JobClass"].push_back(sn.
classes[c].name);
2772 p[
"QLen"].push_back(q);
2773 p[
"Util"].push_back(u);
2774 p[
"RespT"].push_back(rr);
2775 p[
"Tput"].push_back(t);
2778 if (g_json_output) {
2779 p[
"substitutedMean"] = means;
2780 emit_analysis<T>(
"posterior", p, r.
avg.actualmethod);
2794 std::size_t cut = 0;
2795 for (std::size_t i = 0; i < d.
cutoff.size(); ++i) cut = std::max(cut, d.
cutoff[i]);
2797 std::printf(
"SolverCTMC arith=%s method=%s type=%s states=%zu cutoff=%zu\n",
2802 std::printf(
"SolverCTMC arith=%s method=%s type=%s states=%zu\n",
2808 if (!d.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", d.
warning.c_str());
2823 m[
"states"] = d.
chain.space.size();
2824 std::size_t cut = 0;
2825 for (std::size_t i = 0; i < d.
cutoff.size(); ++i) cut = std::max(cut, d.
cutoff[i]);
2826 if (cut) m[
"cutoff"] = cut;
2841 print_ctmc_banner<T>(a.
sol);
2842 print_avg_table<T>(sn, r);
2847 std::printf(
"%-16s %-14s %12s\n",
"Region",
"JobClass",
"Parked");
2848 for (std::size_t c = 0; c < sn.
nclasses && c < a.
parked.size(); ++c)
2849 std::printf(
"%-16s %-14s %12.6g\n",
"(all)", sn.
classes[c].name.c_str(),
2876 std::size_t held = 0;
2879 "SolverCTMC arith=%s method=%s type=%s states=%lld held=%zu levels=%zu iters=%d "
2880 "encoding=%s exact=%s\n",
2888 print_avg_table<T>(sn, r);
2915 if (!line::ctmc::analyzer_detail::default_init_state(sn, init))
2917 "-a prob reports the probability of the model's DEFAULT INITIAL STATE, and this "
2918 "model's initial marking admits no state; check the class populations against their "
2919 "reference stations");
2927 if (!k.state.empty()) {
2930 "--state is the state of ONE node and needs --node to say which; a bare state "
2931 "vector cannot be matched against a network whose nodes have different widths");
2935 " is not a stateful node, so it holds no state to ask about");
2944 const std::size_t w = d.
chain.space.empty() ? k.state.size()
2945 : d.
chain.space[0].local[isf - 1].size();
2946 if (k.state.size() != w)
2948 "--state has " + std::to_string(k.state.size()) +
" entries but node " +
2949 std::to_string(k.node) +
" encodes its state in " + std::to_string(w) +
2950 "; getProb(node, state) takes the node's whole encoded row, and a shorter one "
2951 "padded with zeros is a different state rather than a partial one (use -a marg "
2952 "for a per-class job-count marginal)");
2954 for (std::size_t i = 0; i < k.state.size(); ++i)
2956 init.
local[isf - 1] = row;
2963 if (g_json_output) {
2965 p[
"type"] =
"ProbAggr";
2969 line::reg::Json st = line::reg::Json::array(), pm = line::reg::Json::array(),
2970 pa = line::reg::Json::array();
2971 for (std::size_t i = 0; i < sn.
nstations; ++i) {
2979 emit_analysis<T>(
"prob", p, d.
actualmethod, ctmc_meta<T>(d));
2982 print_ctmc_banner<T>(d);
2985 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
2986 for (std::size_t i = 0; i < sn.
nstations; ++i)
2987 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
3011 for (std::size_t i = 0; i < filt.size(); ++i)
3012 for (std::size_t r = 0; r < filt[i].size(); ++r) {
3013 line::reg::Json bfrom = line::reg::Json::array(), bto = line::reg::Json::array(),
3014 brate = line::reg::Json::array();
3015 for (std::size_t a = 0; a < n; ++a)
3016 for (std::size_t b = 0; b < n; ++b) {
3018 if (q == 0.0)
continue;
3023 if (bfrom.empty())
continue;
3030 blocks.push_back(e);
3041 const std::size_t n = g.
Q.rows();
3042 std::size_t nnz = 0;
3043 for (std::size_t i = 0; i < n; ++i)
3044 for (std::size_t j = 0; j < n; ++j)
3046 if (g_json_output) {
3048 p[
"type"] =
"InfGen";
3055 line::reg::Json from = line::reg::Json::array(), to = line::reg::Json::array(),
3056 rate = line::reg::Json::array();
3057 for (std::size_t i = 0; i < n; ++i)
3058 for (std::size_t j = 0; j < n; ++j) {
3060 if (q == 0.0)
continue;
3074 for (std::size_t a = 0; a < g.
sync.size() && a < g.
filt.size(); ++a) {
3079 std::size_t fnz = 0;
3080 line::reg::Json ffrom = line::reg::Json::array(), fto = line::reg::Json::array(),
3081 frate = line::reg::Json::array();
3082 for (std::size_t i = 0; i < n; ++i)
3083 for (std::size_t j = 0; j < n; ++j) {
3085 if (q == 0.0)
continue;
3096 e[
"activeNode"] = g.
sync[a].active.node;
3097 e[
"activeClass"] = g.
sync[a].active.cls;
3099 e[
"passiveNode"] = g.
sync[a].passive.node;
3100 e[
"passiveClass"] = g.
sync[a].passive.cls;
3109 p[
"startFilt"] = aux_filt_json<T>(g.
start_filt, n);
3110 p[
"preemptFilt"] = aux_filt_json<T>(g.
preempt_filt, n);
3111 emit_analysis<T>(
"gen", p, d.
actualmethod, ctmc_meta<T>(d));
3114 print_ctmc_banner<T>(d);
3115 std::printf(
"InfGen events=%zu nnz=%zu\n", g.
sync.size(), nnz);
3116 std::printf(
"%8s %8s %16s\n",
"From",
"To",
"Rate");
3117 for (std::size_t i = 0; i < n; ++i)
3118 for (std::size_t j = 0; j < n; ++j) {
3120 if (q != 0.0) std::printf(
"%8zu %8zu %16.10g\n", i + 1, j + 1, q);
3122 std::printf(
"%6s %-10s %6s %6s %-10s %6s %6s %8s\n",
"Event",
"ActEvent",
"ActNode",
"ActCls",
3123 "PasEvent",
"PasNode",
"PasCls",
"Nnz");
3124 for (std::size_t a = 0; a < g.
sync.size() && a < g.
filt.size(); ++a) {
3125 std::size_t fnz = 0;
3126 for (std::size_t i = 0; i < n; ++i)
3127 for (std::size_t j = 0; j < n; ++j)
3129 std::printf(
"%6zu %-10s %6zu %6zu %-10s %6zu %6zu %8zu\n", a + 1,
3132 g.
sync[a].passive.node, g.
sync[a].passive.cls, fnz);
3143 if (g_json_output) {
3145 p[
"type"] =
"StateSpace";
3150 p[
"space"] = matrix_json(s.
flat);
3151 p[
"spaceAggr"] = matrix_json(A);
3159 for (std::size_t f = 0; f < s.
local.size(); ++f) loc.push_back(matrix_json(s.
local[f]));
3160 p[
"localSpace"] = loc;
3164 p[
"pi"] = vector_json(d.
pi);
3165 emit_analysis<T>(
"states", p, d.
actualmethod, ctmc_meta<T>(d));
3168 print_ctmc_banner<T>(d);
3171 std::printf(
"NodeWidths");
3174 std::printf(
"%8s %12s %s\n",
"State",
"Prob",
"Detailed | Aggregate");
3175 for (std::size_t i = 0; i < s.
flat.rows(); ++i) {
3177 for (std::size_t c = 0; c < s.
flat.cols(); ++c)
3180 for (std::size_t c = 0; c < A.
cols(); ++c)
3207 std::vector<bool> queueing(M,
false);
3208 for (std::size_t i = 0; i < M; ++i)
3214 for (std::size_t s = 0; s < reward.size(); ++s)
3215 for (std::size_t i = 0; i < M; ++i)
3217 for (std::size_t c = 0; c < K; ++c) reward[s] += A(s, i * K + c);
3219 std::vector<line::ctmc::CtmcSensParam<T> > params;
3220 std::vector<std::string> skipped;
3221 for (std::size_t i = 0; i < M; ++i) {
3223 for (std::size_t c = 0; c < K; ++c) {
3226 if (!(mu > 0.0))
continue;
3227 const std::string nm =
3230 skipped.push_back(nm);
3240 params.push_back(p);
3245 "-a sens found no exponential service rate to differentiate: every enabled "
3246 "(station, class) pair carries a non-exponential distribution, and replacing one with "
3247 "an exponential of the perturbed rate would change the model rather than a parameter "
3250 const std::vector<line::ctmc::CtmcSensRank<T> > rank =
3252 if (g_json_output) {
3254 p[
"type"] =
"SensRanking";
3259 p[
"reward"] =
"mean number of jobs at the queueing stations";
3260 line::reg::Json par = line::reg::Json::array(), val = line::reg::Json::array(),
3261 S = line::reg::Json::array(), SS = line::reg::Json::array();
3262 for (std::size_t l = 0; l < rank.size(); ++l) {
3263 par.push_back(rank[l].parameter);
3264 val.push_back(rank[l].value);
3268 if (rank[l].scaled_valid)
3273 p[
"Parameter"] = par;
3276 p[
"ScaledSens"] = SS;
3278 for (std::size_t l = 0; l < skipped.size(); ++l) sk.push_back(skipped[l]);
3280 emit_analysis<T>(
"sens", p, d.
actualmethod, ctmc_meta<T>(d));
3283 print_ctmc_banner<T>(d);
3284 std::printf(
"Reward mean number of jobs at the queueing stations\n");
3285 std::printf(
"%-28s %14s %16s %16s\n",
"Parameter",
"Value",
"Sens",
"ScaledSens");
3286 for (std::size_t l = 0; l < rank.size(); ++l) {
3287 if (rank[l].scaled_valid)
3288 std::printf(
"%-28s %14.6g %16.8g %16.8g\n", rank[l].parameter.c_str(), rank[l].value,
3294 std::printf(
"%-28s %14.6g %16.8g %16s\n", rank[l].parameter.c_str(), rank[l].value,
3297 for (std::size_t l = 0; l < skipped.size(); ++l)
3298 std::printf(
"Skipped %s: service is not exponential\n", skipped[l].c_str());
3305 std::vector<std::string> names;
3307 if (g_json_output) {
3309 p[
"type"] =
"AvgReward";
3311 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
3313 p[
"E"] = vector_json(r);
3316 emit_analysis<T>(
"reward", p, std::string());
3320 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
3321 for (std::size_t l = 0; l < r.size(); ++l)
3343 if (k.reward_name.empty())
3345 "-a reward-value returns the value function of ONE reward and needs --reward-name to "
3346 "say which; -a reward returns the steady-state expectation of every declared reward");
3348 std::size_t which = rr.
names.size();
3349 for (std::size_t l = 0; l < rr.
names.size(); ++l)
3350 if (rr.
names[l] == k.reward_name) which = l;
3351 if (which == rr.
names.size()) {
3353 for (std::size_t l = 0; l < rr.
names.size(); ++l)
3354 avail += (l ?
", " :
"") + rr.
names[l];
3356 "' is not declared by this model; it declares: " +
3357 (avail.empty() ? std::string(
"(none)") : avail));
3361 if (g_json_output) {
3363 p[
"type"] =
"RewardValueFunction";
3365 p[
"Reward"] = rr.
names[which];
3366 p[
"steps"] = V.
rows();
3367 p[
"states"] = V.
cols();
3368 p[
"t"] = vector_json(rr.
t);
3369 p[
"V"] = matrix_json(V);
3371 emit_analysis<T>(
"rewardvalue", p, std::string());
3374 std::printf(
"SolverCTMC arith=%s reward=%s steps=%zu states=%zu\n",
3376 std::printf(
"%-10s %-10s %20s\n",
"Step",
"State",
"V");
3377 for (std::size_t i = 0; i < V.
rows(); ++i)
3378 for (std::size_t j = 0; j < V.
cols(); ++j)
3401 (void)sn; (void)opt; (void)k;
3403 "-a tranreward integrates the forward equation, which needs transcendental "
3404 "arithmetic; rerun with --arith double or --arith real");
3408 "-a tranreward integrates E[r(X(t))] and needs a horizon: pass --tspan <t0>:<t1>");
3409 std::vector<std::string> names;
3414 if (g_json_output) {
3416 p[
"type"] =
"TranReward";
3419 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
3421 p[
"t"] = vector_json<T>(t);
3423 for (std::size_t l = 0; l < r.size(); ++l) e.push_back(vector_json<T>(r[l]));
3425 emit_analysis<T>(
"tranreward", p, std::string());
3428 std::printf(
"SolverCTMC arith=%s rewards=%zu points=%zu tspan=[%g,%g]\n",
3430 std::printf(
"%-16s",
"t");
3431 for (std::size_t l = 0; l < names.size(); ++l) std::printf(
" %16s", names[l].c_str());
3433 for (std::size_t i = 0; i < t.size(); ++i) {
3435 for (std::size_t l = 0; l < r.size(); ++l)
3457 "-a tranprob integrates pi(t) and needs a horizon: pass --tspan <t0>:<t1>");
3466 if (g_json_output) {
3468 p[
"type"] =
"TranProb";
3470 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
3471 if (k.node) p[
"node"] = k.node - 1;
3473 span.push_back(k.t0);
3474 span.push_back(k.t1);
3479 p[
"t"] = vector_json(det.
t);
3484 p[
"labels"] = matrix_json(det.
labels);
3485 p[
"labelsAggr"] = matrix_json(agg.
labels);
3486 p[
"pit"] = matrix_json(det.
pit);
3487 p[
"pitAggr"] = matrix_json(agg.
pit);
3488 emit_analysis<T>(
"tranprob", p, tr.chain.actualmethod, ctmc_meta<T>(tr.chain));
3491 print_ctmc_banner<T>(tr.chain);
3492 std::printf(
"TranProb times=%zu tspan=%g:%g scope=%s\n", det.
t.size(), k.t0, k.t1,
3493 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
3496 std::printf(
"%8s %s\n",
"State",
"Detailed | Aggregate");
3497 for (std::size_t s = 0; s < det.
labels.rows(); ++s) {
3498 std::printf(
"%8zu ", s + 1);
3499 for (std::size_t c = 0; c < det.
labels.cols(); ++c)
3502 for (std::size_t c = 0; c < agg.
labels.cols(); ++c)
3506 std::printf(
"%14s",
"Time");
3507 for (std::size_t s = 0; s < det.
pit.cols(); ++s) std::printf(
" %12zu", s + 1);
3509 for (std::size_t i = 0; i < det.
t.size(); ++i) {
3511 for (std::size_t s = 0; s < det.
pit.cols(); ++s)
3540 "-a tran integrates the forward equation and needs a horizon: pass --tspan <t0>:<t1>");
3545 if (g_json_output) {
3547 p[
"type"] =
"TranAvgTable";
3554 for (std::size_t i = 0; i < M; ++i)
3555 for (std::size_t c = 0; c < K; ++c) {
3562 e[
"Station"] = sn.
stations[i].name;
3563 e[
"JobClass"] = sn.
classes[c].name;
3567 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
3568 x = line::reg::Json::array();
3569 for (std::size_t j = 0; j < nt; ++j) {
3580 emit_analysis<T>(
"tran", p, tr.chain.actualmethod, ctmc_meta<T>(tr.chain));
3583 print_ctmc_banner<T>(tr.chain);
3584 std::printf(
"TranAvg times=%zu tspan=%g:%g\n", nt, k.t0, k.t1);
3585 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
3587 for (std::size_t i = 0; i < M; ++i)
3588 for (std::size_t c = 0; c < K; ++c) {
3590 for (std::size_t j = 0; j < nt; ++j)
3591 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
3615 const std::size_t nevents = k.events ? k.events : (k.samples ? k.samples : 1000);
3616 const unsigned long seed = k.seed ? k.seed : 23000;
3625 if (g_json_output) {
3627 p[
"type"] =
"SamplePath";
3631 p[
"events"] = nevents;
3633 p[
"drawn"] = path.
state.size();
3634 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
3635 if (k.node) p[
"node"] = k.node - 1;
3636 p[
"t"] = vector_json(path.
t);
3637 p[
"state"] = index_json(path.
state);
3639 for (std::size_t i = 0; i < path.
state.size(); ++i) {
3643 if (i < path.
event.size() && path.
event[i] !=
static_cast<std::size_t
>(-1))
3644 ev.push_back(path.
event[i]);
3649 p[
"sysAggr"] = matrix_json(A);
3657 p[
"space"] = matrix_json(s.
flat);
3661 p[
"nodeState"] = matrix_json(L);
3662 p[
"nodeAggr"] = matrix_json(LA);
3664 emit_analysis<T>(
"sample", p, path.
chain.actualmethod, ctmc_meta<T>(path.
chain));
3669 std::printf(
"SolverCTMC arith=%s states=%zu events=%zu seed=%lu drawn=%zu scope=%s\n",
3671 path.
state.size(), k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
3672 std::printf(
"%14s %8s %8s %s\n",
"Time",
"State",
"Event",
3673 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
3674 for (std::size_t i = 0; i < path.
state.size(); ++i) {
3675 const std::size_t ev = i < path.
event.size() ? path.
event[i] :
static_cast<std::size_t
>(-1);
3677 if (ev ==
static_cast<std::size_t
>(-1))
3678 std::printf(
"%8s ",
"absorb");
3680 std::printf(
"%8zu ", ev + 1);
3681 for (std::size_t c = 0; c < A.
cols(); ++c)
3685 for (std::size_t c = 0; c < L.
cols(); ++c)
3688 for (std::size_t c = 0; c < LA.
cols(); ++c)
3699 const std::vector<std::vector<line::ctmc::CdfCurve<T> > > RD =
3702 if (g_json_output) {
3704 p[
"type"] =
"CdfRespT";
3711 for (std::size_t i = 0; i < RD.size(); ++i)
3712 for (std::size_t c = 0; c < RD[i].size(); ++c) {
3716 if (RD[i][c].empty())
continue;
3718 e[
"Station"] = sn.
stations[i].name;
3719 e[
"JobClass"] = sn.
classes[c].name;
3722 e[
"t"] = vector_json(RD[i][c].t);
3723 e[
"F"] = vector_json(RD[i][c].F);
3728 for (std::size_t c = 0; c < RS.size(); ++c) {
3729 if (RS[c].empty())
continue;
3732 e[
"t"] = vector_json(RS[c].t);
3733 e[
"F"] = vector_json(RS[c].F);
3741 emit_analysis<T>(
"cdf", p, std::string());
3745 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
3746 for (std::size_t i = 0; i < RD.size(); ++i)
3747 for (std::size_t c = 0; c < RD[i].size(); ++c) {
3750 if (RD[i][c].empty())
continue;
3751 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
3752 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
3757 std::printf(
"%-16s %-14s %14s %14s\n",
"System",
"Chain",
"Time",
"F(t)");
3758 for (std::size_t c = 0; c < RS.size(); ++c) {
3759 if (RS[c].empty())
continue;
3760 for (std::size_t j = 0; j < RS[c].t.size(); ++j)
3761 std::printf(
"%-16s %-14zu %14.8g %14.10g\n",
"(system)", c + 1,
3776 std::vector<std::vector<double>> rows;
3777 std::size_t pos = 0;
3778 while (pos <= spec.size()) {
3779 std::size_t semi = spec.find(
';', pos);
3780 if (semi == std::string::npos) semi = spec.size();
3781 std::string rowtxt = spec.substr(pos, semi - pos);
3782 std::vector<double> row;
3784 while (p2 <= rowtxt.size()) {
3785 std::size_t comma = rowtxt.find(
',', p2);
3786 if (comma == std::string::npos) comma = rowtxt.size();
3787 std::string cell = rowtxt.substr(p2, comma - p2);
3788 if (!cell.empty()) {
3790 const double v = std::strtod(cell.c_str(), &endp);
3791 if (endp == cell.c_str() || *endp !=
'\0')
3793 "' is not a number");
3798 if (!row.empty()) rows.push_back(row);
3802 for (std::size_t i = 1; i < rows.size(); ++i)
3803 if (rows[i].size() != rows[0].size())
3805 ": every state row must have the same width");
3807 for (std::size_t i = 0; i < rows.size(); ++i)
3808 for (std::size_t j = 0; j < rows[i].size(); ++j) out(i, j) = rows[i][j];
3821 if (k.passage_into.empty())
3823 "-a firstpasst times the passage INTO a state set and needs --passage-into; name it "
3824 "as 1-based rows of the state space ('3,5') or as state rows ('0,2;1,1')");
3827 const std::string method = k.passage_method.empty() ?
"expm" : k.passage_method;
3832 if (g_json_output) {
3834 p[
"type"] =
"CdfFirstPassT";
3839 line::reg::Json src = line::reg::Json::array(), tgt = line::reg::Json::array();
3840 for (std::size_t i : fp.
source) src.push_back(
static_cast<double>(i));
3841 for (std::size_t i : fp.
target) tgt.push_back(
static_cast<double>(i));
3844 emit_analysis<T>(
"firstpasst", p, method);
3847 std::printf(
"SolverCTMC arith=%s method=%s getCdfFirstPassT\n",
3849 std::printf(
"source states: %zu%s, target states: %zu\n", fp.
source.size(),
3850 fp.
source.empty() ?
" (conditional stationary law)" :
"", fp.
target.size());
3851 std::printf(
"%14s %14s %14s\n",
"Time",
"F(t)",
"f(t)");
3852 for (std::size_t j = 0; j < fp.
t.size(); j += 111)
3853 std::printf(
"%14.8g %14.10g %14.10g\n", fp.
t[j], fp.
F[j], fp.
f[j]);
3854 std::printf(
"%14.8g %14.10g %14.10g\n", fp.
t.back(), fp.
F.back(), fp.
f.back());
3870 if (k.passage_into.empty())
3872 "-a firstpasstmom times the passage INTO a state set and needs --passage-into; name "
3873 "it as 1-based rows of the state space ('3,5') or as state rows ('0,2;1,1')");
3876 const std::size_t nmax = (k.passage_orders > 0) ? k.passage_orders : 3;
3881 if (g_json_output) {
3883 p[
"type"] =
"FirstPassTMoments";
3886 for (std::size_t i = 0; i < fm.
m.size(); ++i)
3890 for (std::size_t i = 0; i < fm.
mall.rows(); ++i) {
3892 for (std::size_t j = 0; j < fm.
mall.cols(); ++j)
3894 mall.push_back(row);
3897 line::reg::Json src = line::reg::Json::array(), tgt = line::reg::Json::array();
3898 for (std::size_t i : fm.
source) src.push_back(
static_cast<double>(i));
3899 for (std::size_t i : fm.
target) tgt.push_back(
static_cast<double>(i));
3902 emit_analysis<T>(
"firstpasstmom", p,
"moments");
3906 std::printf(
"source states: %zu%s, target states: %zu\n", fm.
source.size(),
3907 fm.
source.empty() ?
" (conditional stationary law)" :
"", fm.
target.size());
3908 std::printf(
"%8s %20s\n",
"Order",
"Moment");
3909 for (std::size_t i = 0; i < fm.
m.size(); ++i)
3935std::vector<line::ctmc::CtmcRateSched> parse_rate_sched(
const std::string& arg,
3937 std::string text = arg;
3938 const std::size_t first = arg.find_first_not_of(
" \t\r\n");
3939 if (first == std::string::npos || (arg[first] !=
'[' && arg[first] !=
'{')) {
3940 std::ifstream in(arg.c_str());
3943 "' is neither inline JSON nor a readable file");
3944 std::ostringstream ss;
3948 line::io::detail::json root;
3950 root = line::io::detail::json::parse(text);
3951 }
catch (
const std::exception& e) {
3952 throw line::InputError(std::string(
"--rate-sched: not valid JSON: ") + e.what());
3954 if (root.is_object()) root = line::io::detail::json::array({root});
3955 if (!root.is_array() || root.empty())
3956 throw line::InputError(
"--rate-sched: expected a non-empty array of schedule objects");
3958 const auto resolve = [](
const line::io::detail::json& v,
const std::vector<std::string>& names,
3959 const char* what) -> std::size_t {
3960 if (v.is_number_integer()) {
3961 const long long i = v.get<
long long>();
3962 if (i < 1 ||
static_cast<std::size_t
>(i) > names.size())
3964 std::to_string(i) +
" is out of range (1-based)");
3965 return static_cast<std::size_t
>(i);
3967 if (v.is_string()) {
3968 const std::string nm = v.get<std::string>();
3969 for (std::size_t i = 0; i < names.size(); ++i)
3970 if (names[i] == nm)
return i + 1;
3971 throw line::InputError(std::string(
"--rate-sched: no ") + what +
" named '" + nm +
"'");
3974 " must be a name or a 1-based index");
3976 std::vector<std::string> stations, classes;
3977 for (std::size_t i = 0; i < sn.
stations.size(); ++i)
3979 for (
const auto& c : sn.
classes) classes.push_back(c.name);
3981 std::vector<line::ctmc::CtmcRateSched> out;
3982 for (
const auto& e : root) {
3983 if (!e.is_object() || !e.contains(
"station") || !e.contains(
"class") ||
3984 !e.contains(
"tgrid") || !e.contains(
"rates"))
3986 "--rate-sched: every entry needs station, class, tgrid and rates");
3988 rs.
station = resolve(e.at(
"station"), stations,
"station");
3989 rs.
cls = resolve(e.at(
"class"), classes,
"class");
3990 rs.
tgrid = e.at(
"tgrid").get<std::vector<double>>();
3991 rs.
rates = e.at(
"rates").get<std::vector<double>>();
3992 if (e.contains(
"nominal") && !e.at(
"nominal").is_null())
3993 rs.
nominal = e.at(
"nominal").get<
double>();
4000int solve_model_ctmc(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
4003 if (!k.method.empty()) opt.
method = k.method;
4004 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4006 opt.
force = k.force;
4007 if (k.timestep > 0.0) opt.
timestep = k.timestep;
4010 if (!k.transient_method.empty()) opt.
transient_method = k.transient_method;
4011 if (k.fau_epsilon > 0.0) opt.
fau_epsilon = k.fau_epsilon;
4012 if (k.fau_delta >= 0.0) opt.
fau_delta = k.fau_delta;
4013 if (k.ctmc_tv_ngrid > 0) opt.
ctmc_tv_ngrid = k.ctmc_tv_ngrid;
4015 if (!k.rate_sched.empty()) opt.
rate_sched = parse_rate_sched(k.rate_sched, sn);
4021 if (opt.
method ==
"mdd") {
4023 if (k.mdd_tol > 0.0) mcdopt.
tol = k.mdd_tol;
4024 if (k.mdd_maxiter > 0) mcdopt.
maxiter = k.mdd_maxiter;
4025 return solve_ctmc_mdd_avg<T>(sn, opt, mcdopt);
4030 if (analysis ==
"avg")
return solve_ctmc_avg<T>(sn, opt);
4031 if (analysis ==
"prob")
return solve_ctmc_prob<T>(sn, opt, k);
4032 if (analysis ==
"gen")
return solve_ctmc_gen<T>(sn, opt);
4033 if (analysis ==
"states")
return solve_ctmc_states<T>(sn, opt);
4034 if (analysis ==
"sens")
return solve_ctmc_sens<T>(sn, opt);
4035 if (analysis ==
"reward")
return solve_ctmc_reward<T>(sn, opt);
4036 if (analysis ==
"rewardvalue")
return solve_ctmc_reward_value<T>(sn, opt, k);
4044 "the -a " + analysis +
4045 " analysis integrates the forward equation, draws exponential clocks or takes a matrix "
4046 "exponential, none of which exists in exact rational arithmetic; rerun with --arith "
4047 "double or --arith real");
4049 if (analysis ==
"tran")
return solve_ctmc_tran<T>(sn, opt, k);
4050 if (analysis ==
"tranprob")
return solve_ctmc_tranprob<T>(sn, opt, k);
4051 if (analysis ==
"tranreward")
return solve_ctmc_tran_reward<T>(sn, opt, k);
4052 if (analysis ==
"sample")
return solve_ctmc_sample<T>(sn, opt, k);
4053 if (analysis ==
"firstpasst")
return solve_ctmc_firstpasst<T>(sn, opt, k);
4054 if (analysis ==
"firstpasstmom")
return solve_ctmc_firstpasst_moments<T>(sn, opt, k);
4055 return solve_ctmc_cdf<T>(sn, opt);
4075int solve_model_ssa_prob(
const std::string& file,
const Knobs& k) {
4084 if (k.samples) opt.
samples = k.samples;
4085 if (k.seed) opt.
seed = k.seed;
4086 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4087 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4090 if (g_json_output) {
4092 p[
"type"] =
"ProbAggr";
4098 p[
"ProbSysSeen"] = r.
sys.
seen;
4100 line::reg::Json st = line::reg::Json::array(), pm = line::reg::Json::array(),
4101 pa = line::reg::Json::array(), sm = line::reg::Json::array();
4102 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4104 pm.push_back(r.
marg[i].prob);
4105 pa.push_back(r.
aggr[i].prob);
4106 sm.push_back(r.
marg[i].seen);
4112 emit_analysis<T>(
"prob", p,
"serial");
4115 std::printf(
"SolverSSA arith=%s method=serial samples=%zu seed=%lu time=%.6g\n",
4117 std::printf(
"ProbSys = %.8g%s\n", r.
sys.
prob, r.
sys.
seen ?
"" :
" (state never visited)");
4120 std::printf(
"%-20s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
4121 for (std::size_t i = 0; i < sn.
nstations; ++i)
4122 std::printf(
"%-20s %14.8g %14.8g\n", sn.
stations[i].name.c_str(), r.
marg[i].prob,
4140int solve_model_ssa_sample(
const std::string& file,
const Knobs& k) {
4145 opt.
samples = k.events ? k.events : (k.samples ? k.samples : 1000);
4146 if (k.seed) opt.
seed = k.seed;
4147 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4148 if (k.cutoff >= 0.0) opt.
cutoff = k.cutoff;
4156 if (g_json_output) {
4158 p[
"type"] =
"SamplePath";
4161 p[
"seed"] = sys.
seed;
4162 p[
"drawn"] = sys.
t.size();
4163 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
4164 if (k.node) p[
"node"] = k.node - 1;
4165 p[
"t"] = vector_json(sys.
t);
4166 p[
"event"] = index_json(sys.
event);
4167 p[
"state"] = matrix_json(sys.
state);
4176 w.push_back(sim.
run.space.empty() ? 0 : sim.
run.space[0].local[f].size());
4177 p[
"NodeWidths"] = w;
4179 p[
"sysAggr"] = matrix_json(sys.
aggr);
4181 p[
"nodeState"] = matrix_json(nodep.
state);
4182 p[
"nodeAggr"] = matrix_json(nodep.
aggr);
4184 emit_analysis<T>(
"sample", p,
"serial");
4187 std::printf(
"SolverSSA arith=%s method=serial events=%zu seed=%lu drawn=%zu scope=%s\n",
4189 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
4190 std::printf(
"%14s %8s %s\n",
"Time",
"Event",
4191 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
4192 for (std::size_t i = 0; i < sys.
t.size(); ++i) {
4193 std::printf(
"%14.8g %8zu ", sys.
t[i], sys.
event[i]);
4194 for (std::size_t c = 0; c < sys.
aggr.cols(); ++c)
4198 for (std::size_t c = 0; c < nodep.
state.cols(); ++c)
4201 for (std::size_t c = 0; c < nodep.
aggr.cols(); ++c)
4221 for (std::size_t i = 0; i < m.
rows(); ++i)
4222 for (std::size_t j = 0; j < m.
cols(); ++j)
4248int solve_model_ssa(
const std::string& file,
const Knobs& k) {
4251 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4252 if (k.samples) opt.
samples = k.samples;
4253 if (k.seed) opt.
seed = k.seed;
4254 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4260 std::vector<line::ssa::SsaCacheRatio> cacheratio;
4272 std::printf(
"SolverSSA arith=%s method=%s type=%s samples=%zu seed=%lu time=%.6g\n",
4293 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
4303 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
4308 row.w = line::num_traits<T>::to_double(WN(i, c));
4312 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
4314 : line::num_traits<T>::to_double(AN(i, c));
4324 if (!k.method.empty()) opt.
method = k.method;
4325 if (k.tol >= 0.0) opt.
tol = k.tol;
4326 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4327 if (k.iter_max >= 0) opt.
iter_max =
static_cast<std::size_t
>(k.iter_max);
4333 if (k.pstar > 0.0) {
4334 opt.
pstar = k.pstar;
4354int solve_model_fluid(
const std::string& file,
const Knobs& k) {
4372 std::printf(
"SolverFluid arith=%s method=%s type=%s iters=%d\n",
4394 &refreshed, &cache);
4415 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
4416 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
4421 row.w = line::num_traits<T>::to_double(WN(i, c));
4427 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
4429 : line::num_traits<T>::to_double(AN(i, c));
4450int solve_model_fluid_statevec(
const std::string& file,
const Knobs& k) {
4457 "-a statevec reports the converged ODE state and this solve produced none; the rmf "
4458 "and closing branches integrate a drift and fill it, so a branch that returns means "
4459 "directly has no state vector to report");
4463 std::vector<std::size_t> ist, cls, phs;
4464 for (std::size_t i = 0; i < sn.
nstations; ++i)
4465 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4466 const std::size_t np = sn.
phases_of(i + 1, c + 1);
4467 for (std::size_t j = 0; j < np; ++j) {
4476 const bool labelled = ist.size() == r.
xvec.size();
4478 if (g_json_output) {
4480 p[
"type"] =
"FluidStateVec";
4482 p[
"xvec"] = vector_json(r.
xvec);
4483 p[
"labelled"] = labelled;
4485 line::reg::Json st = line::reg::Json::array(), cl = line::reg::Json::array(),
4486 ph = line::reg::Json::array();
4487 for (std::size_t j = 0; j < ist.size(); ++j) {
4488 st.push_back(sn.
stations[ist[j]].name);
4489 cl.push_back(sn.
classes[cls[j]].name);
4490 ph.push_back(phs[j]);
4496 emit_analysis<T>(
"statevec", p, r.
method);
4502 std::printf(
"# the ODE state is %zu wide and the (station, class, phase) layout accounts "
4503 "for %zu; the coordinates are printed unlabelled\n",
4504 r.
xvec.size(), ist.size());
4505 std::printf(
"%-10s %20s\n",
"Index",
"x");
4506 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
4507 std::printf(
"%-10zu %20.10g\n", j, r.
xvec[j]);
4510 std::printf(
"%-16s %-14s %-8s %20s\n",
"Station",
"JobClass",
"Phase",
"x");
4511 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
4512 std::printf(
"%-16s %-14s %-8zu %20.10g\n", sn.
stations[ist[j]].name.c_str(),
4513 sn.
classes[cls[j]].name.c_str(), phs[j], r.
xvec[j]);
4551 line::env::dispatch_detail::env_take_statevec(out);
4562int solve_model_env(
const std::string& file,
const Knobs& k) {
4565 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4566 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4567 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4569 if (k.tran_points) opt.
tran_points = k.tran_points;
4570 if (k.tol >= 0.0) opt.
stage.
tol = k.tol;
4588 if (!k.stage_solver.empty()) opt.
stage_solver = k.stage_solver;
4609 std::printf(
"SolverENV arith=%s method=%s stages=%zu (closed-form limit)\n",
4611 else if (r.
method ==
"statevec")
4612 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g iters=%d%s\n",
4619 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g points=%zu iters=%d%s\n",
4627 if (!r.
cache.empty()) extra[
"Cache"] = cache_extra_json<T>(r.
cache);
4630 [&](std::size_t i, std::size_t c) {
4632 row.q = line::num_traits<T>::to_double(r.QN(i, c));
4633 row.u = line::num_traits<T>::to_double(r.UN(i, c));
4634 row.t = line::num_traits<T>::to_double(r.TN(i, c));
4635 row.r = std::numeric_limits<double>::quiet_NaN();
4636 row.a = std::numeric_limits<double>::quiet_NaN();
4637 row.w = row.q / row.t;
4641 if (!g_json_output) print_cache_rows<T>(sn, r.
cache);
4658int solve_model_fluid_var(
const std::string& file,
const Knobs& k) {
4665 "-a var needs a fluid method that computes a second moment: 'minnormal', 'refined' or "
4666 "'dae' for the stationary covariance, 'kp' for the covariance along the trajectory. "
4668 r.
method +
"' method integrates the mean only");
4670 if (g_json_output) {
4672 p[
"type"] =
"QueueLengthVariance";
4674 p[
"Station"] = line::reg::Json::array();
4675 p[
"JobClass"] = line::reg::Json::array();
4676 p[
"QVar"] = line::reg::Json::array();
4677 p[
"QStd"] = line::reg::Json::array();
4678 for (std::size_t i = 0; i < sn.
nstations; ++i)
4679 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4681 p[
"Station"].push_back(sn.
stations[i].name);
4682 p[
"JobClass"].push_back(sn.
classes[c].name);
4687 emit_analysis<T>(
"var", p, r.
method);
4692 std::printf(
"%-16s %-14s %12s %12s\n",
"Station",
"JobClass",
"QVar",
"QStd");
4693 for (std::size_t i = 0; i < sn.
nstations; ++i)
4694 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4696 std::printf(
"%-16s %-14s %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4712int solve_model_fluid_odes(
const std::string& file,
const Knobs& k) {
4715 if (!k.method.empty()) opt.
method = k.method;
4716 if (k.tol >= 0.0) opt.
tol = k.tol;
4723 const std::string notation = k.notation.empty() ?
"scalar" : k.notation;
4725 if (g_json_output) {
4728 p[
"notation"] = notation;
4733 emit_analysis<T>(
"odes", p,
4734 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
4737 std::printf(
"%s\n", tex.c_str());
4753int solve_model_fluid_jacobian(
const std::string& file,
const Knobs& k) {
4756 if (!k.method.empty()) opt.
method = k.method;
4763 std::string m = opt.
method;
4764 if (m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
4768 if (!k.symbolic.empty()) symopt.
backend = k.symbolic;
4772 if (g_json_output) {
4774 p[
"type"] =
"Jacobian";
4775 p[
"engine"] = jac.
engine;
4779 for (std::size_t i = 0; i < jac.
J.size(); ++i) rows.push_back(
line::reg::Json(jac.
J[i]));
4780 p[
"jacobian"] = rows;
4785 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e) {
4787 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4789 one[it->first] = it->second;
4792 p[
"equilibria"] = eqs;
4793 emit_analysis<T>(
"jacobian", p,
4794 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
4798 std::printf(
"engine=%s states=%zu\n", jac.
engine.c_str(), jac.
vars.size());
4799 for (std::size_t i = 0; i < jac.
rhs.size(); ++i)
4800 std::printf(
"d%s/dt = %s\n", jac.
vars[i].c_str(), jac.
rhs[i].c_str());
4801 for (std::size_t i = 0; i < jac.
J.size(); ++i)
4802 for (std::size_t j = 0; j < jac.
J[i].size(); ++j) {
4805 std::printf(
"J[%s,%s] = %s\n", jac.
vars[i].c_str(), jac.
vars[j].c_str(),
4806 jac.
J[i][j].c_str());
4810 std::printf(
"equilibria: none in closed form (the solve found none, which is not a "
4811 "proof that none exist)\n");
4812 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e)
4813 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4815 std::printf(
"equilibrium %zu: %s = %s\n", e + 1, it->first.c_str(),
4816 it->second.c_str());
4833int solve_model_fluid_tranvar(
const std::string& file,
const Knobs& k) {
4841 if (g_json_output) {
4843 p[
"type"] =
"TranAvgVarTable";
4845 p[
"t0"] = tr.t.front();
4846 p[
"t1"] = tr.t.back();
4848 for (std::size_t j = 0; j < tr.t.size(); ++j) ts.push_back(tr.t[j]);
4851 for (std::size_t i = 0; i < sn.
nstations; ++i)
4852 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4855 e[
"Station"] = sn.
stations[i].name;
4856 e[
"JobClass"] = sn.
classes[c].name;
4860 for (std::size_t j = 0; j < tr.QVar.size(); ++j) v.push_back(tr.QVar[j](i, c));
4869 for (std::size_t j = 0; j < tr.Sigma.size(); ++j)
4870 sig.push_back(matrix_json<double>(tr.Sigma[j]));
4872 emit_analysis<T>(
"tranvar", p,
"kp");
4875 std::printf(
"SolverFluid arith=%s method=kp tspan=[%g,%g] points=%zu dim=%zu\n",
4877 tr.Sigma.empty() ? std::size_t(0) : tr.Sigma.front().rows());
4878 std::printf(
"%-16s %-14s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QVar",
"QStd");
4879 for (std::size_t i = 0; i < sn.
nstations; ++i)
4880 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4882 for (std::size_t j = 0; j < tr.QVar.size(); ++j) {
4883 const double var = tr.QVar[j](i, c);
4884 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4885 sn.
classes[c].name.c_str(), tr.t[j], var,
4886 var >= 0.0 ? std::sqrt(var) : std::numeric_limits<double>::quiet_NaN());
4911int solve_model_fluid_tran(
const std::string& file,
const Knobs& k) {
4915 const std::vector<line::fluid::FluidTranPoint> tr =
4919 if (g_json_output) {
4921 p[
"type"] =
"TranAvgTable";
4924 p[
"t1"] = tr.back().t;
4926 for (std::size_t j = 0; j < tr.size(); ++j) ts.push_back(tr[j].t);
4928 for (std::size_t i = 0; i < sn.
nstations; ++i)
4929 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4932 e[
"Station"] = sn.
stations[i].name;
4933 e[
"JobClass"] = sn.
classes[c].name;
4937 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
4938 x = line::reg::Json::array();
4939 for (std::size_t j = 0; j < tr.size(); ++j) {
4940 q.push_back(tr[j].
QN(i, c));
4941 u.push_back(tr[j].
UN(i, c));
4942 x.push_back(tr[j].TN(i, c));
4950 emit_analysis<T>(
"tran", p,
"closing");
4953 std::printf(
"SolverFluid arith=%s method=closing tspan=[0,%g] points=%zu\n",
4955 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
4957 for (std::size_t i = 0; i < sn.
nstations; ++i)
4958 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4960 for (std::size_t j = 0; j < tr.size(); ++j)
4961 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
4963 tr[j].QN(i, c), tr[j].UN(i, c), tr[j].TN(i, c));
4983int solve_model_fluid_prob(
const std::string& file,
const Knobs& k) {
4990 for (std::size_t i = 0; i < sn.
nstations; ++i)
4993 if (g_json_output) {
4995 p[
"type"] =
"ProbAggr";
4997 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
4998 lp = line::reg::Json::array();
4999 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5001 pa.push_back(pr[i]);
5002 lp.push_back(lg[i]);
5006 p[
"logProbAggr"] = lp;
5007 emit_analysis<T>(
"prob", p, r.
method);
5012 std::printf(
"%-16s %14s %14s\n",
"Station",
"ProbAggr",
"logProbAggr");
5013 for (std::size_t i = 0; i < sn.
nstations; ++i)
5014 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(), pr[i], lg[i]);
5029int solve_model_fluid_cdf(
const std::string& file,
const Knobs& k) {
5033 const std::vector<std::vector<line::fluid::FluidPassage> > RD =
5036 if (g_json_output) {
5038 p[
"type"] =
"CdfRespT";
5041 for (std::size_t i = 0; i < RD.size(); ++i)
5042 for (std::size_t c = 0; c < RD[i].size(); ++c) {
5046 if (RD[i][c].t.empty())
continue;
5048 e[
"Station"] = sn.
stations[i].name;
5049 e[
"JobClass"] = sn.
classes[c].name;
5057 emit_analysis<T>(
"cdf", p, std::string());
5061 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5062 for (std::size_t i = 0; i < RD.size(); ++i)
5063 for (std::size_t c = 0; c < RD[i].size(); ++c)
5064 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
5065 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
5066 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].cdf[j]);
5085int solve_model_fluid_aoi(
const std::string& file,
const Knobs& k) {
5091 "-a aoi reports the age of a status-update system and needs the topology the age laws "
5092 "are defined for: " +
5093 (top.
error.empty() ? std::string(
"this model is not one") : top.
error));
5095 const std::string requested = line::fluid::detail::fluid_unqualify(opt.
method);
5096 if (requested !=
"default" && requested !=
"mfq")
5098 "-a aoi is the AoI branch of the 'mfq' method; '" + requested +
5099 "' integrates the mean-field drift and carries no age process");
5104 "-a aoi: the 'mfq' method did not take its AoI branch on this model");
5109 const std::size_t np = 200;
5110 std::vector<double> tv(np), fa(np), fp(np);
5111 for (std::size_t j = 0; j < np; ++j) {
5112 tv[j] = 5.0 * base *
static_cast<double>(j) /
static_cast<double>(np - 1);
5116 const double asd = std::sqrt(std::max(0.0, r.
aoi.
aoi.
var));
5117 const double psd = std::sqrt(std::max(0.0, r.
aoi.
paoi.
var));
5119 if (g_json_output) {
5140 p[
"AoI_A"] = matrix_json(r.
aoi.
aoi.
A);
5143 p[
"PAoI_A"] = matrix_json(r.
aoi.
paoi.
A);
5145 emit_analysis<T>(
"aoi", p, r.
method);
5148 std::printf(
"SolverFluid arith=%s method=mfq system=%s preemption=%.6g\n",
5150 std::printf(
"%-8s %14s %14s %14s\n",
"Metric",
"Mean",
"Var",
"Std");
5153 std::printf(
"%14s %14s %14s\n",
"Time",
"F_AoI(t)",
"F_PAoI(t)");
5154 for (std::size_t j = 0; j < np; ++j)
5155 std::printf(
"%14.8g %14.10g %14.10g\n", tv[j], fa[j], fp[j]);
5166int solve_model_prob(
const std::string& file,
const Knobs& k) {
5169 "the -a prob analysis fits a binomial/product-form law and needs transcendental "
5170 "arithmetic; rerun with --arith double or --arith real");
5174 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5175 if (k.tol >= 0.0) opt.
tol = k.tol;
5176 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5177 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5183 if (g_json_output) {
5185 p[
"type"] =
"ProbAggr";
5188 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array();
5189 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5203 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
5204 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5207 std::printf(
"%-16s %14.10g\n", sn.
stations[i].name.c_str(),
5231int solve_model_marg(
const std::string& file,
const Knobs& k) {
5234 "the -a marg analysis fits a binomial / Poisson / geometric law and needs "
5235 "transcendental arithmetic; rerun with --arith double or --arith real");
5239 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5240 if (k.tol >= 0.0) opt.
tol = k.tol;
5241 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5242 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5247 std::vector<std::size_t> ists;
5251 " exceeds the number of nodes in the model (" +
5253 const std::size_t ist = sn.
nodes[k.node - 1].station;
5256 sn.
nodes[k.node - 1].name +
5257 "') is not a station, and a queue-length distribution is "
5258 "reported per station");
5259 ists.push_back(ist);
5261 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
5263 std::vector<std::size_t> rs;
5267 " exceeds the number of classes in the model");
5268 rs.push_back(k.jobclass);
5270 for (std::size_t c = 0; c < sn.
nclasses; ++c) rs.push_back(c + 1);
5275 std::vector<line::mva::MargResult<T> > curves;
5276 for (std::size_t a = 0; a < ists.size(); ++a)
5277 for (std::size_t b = 0; b < rs.size(); ++b)
5279 k.marg_states, opt.
method));
5281 if (g_json_output) {
5283 p[
"type"] =
"ProbMarg";
5286 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
5287 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
5290 e[
"station"] = ists[a] - 1;
5291 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5292 e[
"jobclass"] = rs[b] - 1;
5293 e[
"JobClass"] = sn.
classes[rs[b] - 1].name;
5295 for (std::size_t n = 0; n < m.
P.size(); ++n)
5296 jobs.push_back(k.marg_states.empty() ?
static_cast<long>(n)
5297 : k.marg_states[n]);
5299 e[
"P"] = vector_json(m.
P);
5300 e[
"logP"] = vector_json(m.
logP);
5303 p[
"marginal"] = arr;
5310 std::printf(
"%-16s %-14s %-8s %16s\n",
"Station",
"JobClass",
"Jobs",
"ProbMarg");
5311 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
5312 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
5314 for (std::size_t n = 0; n < m.
P.size(); ++n)
5315 std::printf(
"%-16s %-14s %-8ld %16.10g\n",
5316 sn.
stations[ists[a] - 1].name.c_str(),
5317 sn.
classes[rs[b] - 1].name.c_str(),
5318 k.marg_states.empty() ?
static_cast<long>(n) : k.marg_states[n],
5357int solve_model_nc_busyp(
const std::string& file,
const Knobs& k) {
5358 if (k.busy_subnet.empty())
5360 "-a busyperiod needs --busyperiod-subnet: the busy period is defined for a named "
5361 "subnetwork of stations, and no default can choose one");
5364 std::vector<std::size_t> subnet;
5365 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t) {
5368 std::to_string(k.busy_subnet[t]) +
", beyond the model's " +
5370 subnet.push_back(k.busy_subnet[t] - 1);
5372 std::vector<std::size_t> orders = k.busy_orders;
5373 if (orders.empty()) orders.push_back(1);
5376 if (g_json_output) {
5378 p[
"type"] =
"BusyPeriod";
5381 for (std::size_t t = 0; t < subnet.size(); ++t) sj.push_back(subnet[t]);
5383 for (std::size_t t = 0; t < orders.size(); ++t) oj.push_back(orders[t]);
5385 for (std::size_t t = 0; t < b.size(); ++t) bj.push_back(b[t]);
5389 emit_analysis<T>(
"busyperiod", p,
"daduna");
5393 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t)
5394 std::printf(
"%s%zu", t ?
"," :
"", k.busy_subnet[t]);
5396 for (std::size_t t = 0; t < orders.size(); ++t)
5397 std::printf(
" order %zu %.10g\n", orders[t], b[t]);
5402int solve_model_normconst(
const std::string& file,
const Knobs& k,
const std::string& solver) {
5407 if (solver ==
"nc") {
5409 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5410 if (k.tol >= 0.0) opt.
tol = k.tol;
5411 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5412 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5415 : std::numeric_limits<double>::quiet_NaN();
5419 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5420 if (k.tol >= 0.0) opt.
tol = k.tol;
5421 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5422 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5426 if (g_json_output) {
5428 p[
"type"] =
"NormConst";
5431 p[
"logNormConstAggr"] = lG;
5432 emit_analysis<T>(
"normconst", p, method);
5435 std::printf(
"Solver%s arith=%s method=%s lognormconst=%.10g\n",
5445 for (std::size_t i = 0; i < sn.
nstations; ++i)
5446 for (std::size_t r = 0; r < sn.
nclasses; ++r)
5462int solve_model_nc_prob(
const std::string& file,
const Knobs& k) {
5465 "the -s nc -a prob analysis exponentiates a difference of log normalizing constants "
5466 "and needs transcendental arithmetic; rerun with --arith double or --arith real");
5470 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5471 if (k.tol >= 0.0) opt.
tol = k.tol;
5472 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5473 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5490 if (!k.state.empty()) {
5493 "--state is the state of ONE node and needs --node to say which");
5494 const std::size_t ist =
5495 k.node <= sn.
nodes.size() ? sn.
nodes[k.node - 1].station : 0;
5498 " is not a station, so it has no queue-length state");
5501 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5506 std::vector<T> row(k.state.size());
5507 for (std::size_t i = 0; i < k.state.size(); ++i)
5511 for (std::size_t c = 0; c < sn.
nclasses; ++c)
5551 std::numeric_limits<double>::quiet_NaN());
5559 std::numeric_limits<double>::quiet_NaN());
5569 if (g_json_output) {
5571 p[
"type"] =
"ProbAggr";
5575 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
5579 line::reg::Json pa = line::reg::Json::array(), pm = line::reg::Json::array();
5580 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5587 emit_analysis<T>(
"prob", p, am);
5594 std::printf(
"ProbSys, Prob and ProbAggr need a load-dependent normalizing constant, "
5595 "which method '%s' does not compute\n",
5600 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
5601 for (std::size_t i = 0; i < sn.
nstations; ++i)
5602 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
5625int solve_model_nc_sysmarg(
const std::string& file,
const Knobs& k) {
5628 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5629 if (k.tol >= 0.0) opt.
tol = k.tol;
5633 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
5634 const double pop = sn.
classes[r].population;
5635 if (!std::isfinite(pop))
5637 "getProbSysMarg requires a closed model: the joint law of the total queue lengths "
5638 "is not defined when a class has an infinite population");
5642 static_cast<int>(sn.
nstations),
static_cast<int>(std::llround(Ntot)));
5644 std::vector<double> P(states.size(), 0.0);
5645 for (std::size_t j = 0; j < states.size(); ++j)
5649 if (g_json_output) {
5651 p[
"type"] =
"ProbSysMarg";
5653 p[
"engine"] = k.method_perm;
5654 line::reg::Json st = line::reg::Json::array(), arr = line::reg::Json::array();
5655 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
5656 for (std::size_t j = 0; j < states.size(); ++j) {
5659 for (std::size_t i = 0; i < sn.
nstations; ++i) n.push_back(states[j][i]);
5666 emit_analysis<T>(
"sysmarg", p, opt.
method);
5670 opt.
method.c_str(), k.method_perm.c_str());
5671 for (std::size_t i = 0; i < sn.
nstations; ++i)
5672 std::printf(
"%10s", sn.
stations[i].name.c_str());
5673 std::printf(
" %14s\n",
"ProbSysMarg");
5675 for (std::size_t j = 0; j < states.size(); ++j) {
5676 for (std::size_t i = 0; i < sn.
nstations; ++i) std::printf(
"%10d", states[j][i]);
5677 std::printf(
" %14.10g\n", P[j]);
5680 std::printf(
"%*s %14.10g\n",
static_cast<int>(10 * sn.
nstations),
"sum", total);
5702int solve_model_nc_marg(
const std::string& file,
const Knobs& k) {
5705 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5706 if (k.tol >= 0.0) opt.
tol = k.tol;
5707 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5708 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5711 std::vector<std::size_t> ists;
5715 " exceeds the number of nodes in the model (" +
5717 const std::size_t ist = sn.
nodes[k.node - 1].station;
5720 sn.
nodes[k.node - 1].name +
5721 "') is not a station, and a queue-length distribution is "
5722 "reported per station");
5723 ists.push_back(ist);
5725 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
5730 std::vector<line::nc::NcQueueLengthDist<T> > curves;
5731 for (std::size_t a = 0; a < ists.size(); ++a)
5734 if (g_json_output) {
5736 p[
"type"] =
"ProbMargAggr";
5739 for (std::size_t a = 0; a < ists.size(); ++a) {
5741 e[
"station"] = ists[a] - 1;
5742 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5743 e[
"P"] = vector_json<T>(curves[a].P);
5744 e[
"logP"] = vector_json<T>(curves[a].logP);
5748 emit_analysis<T>(
"marg", p, opt.
method);
5753 for (std::size_t a = 0; a < ists.size(); ++a) {
5754 std::printf(
"%-16s %-8s %14s %14s\n",
"Station",
"n",
"P",
"logP");
5755 for (std::size_t n = 0; n < curves[a].P.size(); ++n)
5756 std::printf(
"%-16s %-8zu %14.10g %14.10g\n", sn.
stations[ists[a] - 1].name.c_str(), n,
5779int solve_model_nc_cdf(
const std::string& file,
const Knobs& k) {
5782 "the -s nc -a cdf analysis evaluates the sojourn law on a logarithmic time grid and "
5783 "inverts a generating function; it needs transcendental arithmetic, so rerun with "
5784 "--arith double or --arith real");
5788 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5789 if (k.tol >= 0.0) opt.
tol = k.tol;
5790 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5791 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5792 if (!k.cdf_algorithm.empty()) opt.
cdf_algorithm = k.cdf_algorithm;
5795 if (!r.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", r.
warning.c_str());
5797 if (g_json_output) {
5799 p[
"type"] =
"CdfRespT";
5806 for (std::size_t i = 0; i < r.
RD.size(); ++i)
5807 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
5811 if (r.
RD[i][c].empty())
continue;
5813 e[
"Station"] = sn.
stations[i].name;
5814 e[
"JobClass"] = sn.
classes[c].name;
5817 line::reg::Json tt = line::reg::Json::array(), ff = line::reg::Json::array();
5818 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j) {
5827 p[
"tset"] = vector_json(r.
tset);
5832 emit_analysis<T>(
"cdf", p, std::string());
5837 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5838 for (std::size_t i = 0; i < r.
RD.size(); ++i)
5839 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
5840 if (r.
RD[i][c].empty())
continue;
5841 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j)
5842 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
5863double sens_sanitize_signed(
double x) {
5891int solve_model_nc_sens(
const std::string& file,
const Knobs& k) {
5894 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5895 if (k.tol >= 0.0) opt.
tol = k.tol;
5896 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5897 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5901 if (!k.sens_method.empty()) so.
method = k.sens_method;
5902 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
5903 if (k.sens_step > 0.0) so.
step = k.sens_step;
5909 sn, so,
true, [&sn, &opt]() {
5923 if (g_json_output) {
5925 p[
"type"] =
"SensitivityTable";
5927 p[
"branch"] = tbl.
method;
5945 emit_analysis<T>(
"sens", p, tbl.
method ==
"fd" ? opt.
method : std::string());
5952 std::printf(
"%-16s %-14s %14s %14s %14s %14s\n",
"Station",
"JobClass",
"dTput_dRate",
5953 "dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
5955 std::printf(
"%-16s %-14s %14.6g %14.6g %14.6g %14.6g\n", r.
station.c_str(),
5983 if (k.samples) o.
samples = k.samples;
5984 if (k.seed) o.
seed =
static_cast<long>(k.seed);
5985 if (!k.method.empty()) o.
method = k.method;
5986 if (!k.ldes_tranfilter.empty()) o.
tranfilter = k.ldes_tranfilter;
5987 if (k.ldes_warmupfrac >= 0.0) o.
warmupfrac = k.ldes_warmupfrac;
5988 if (!k.ldes_cimethod.empty()) o.
cimethod = k.ldes_cimethod;
5989 if (k.ldes_cnvgon) o.
cnvgon =
true;
5990 if (k.ldes_cnvgtol > 0.0) o.
cnvgtol = k.ldes_cnvgtol;
5991 if (k.ldes_slotted) o.
slotted =
true;
5992 if (k.ldes_slotlength > 0.0) {
5996 if (k.ldes_replications > 0) o.
replications = k.ldes_replications;
5997 if (k.ldes_numthreads > 0) o.
numthreads = k.ldes_numthreads;
5998 if (k.ldes_maxtime > 0.0) o.
timeout = k.ldes_maxtime;
5999 if (!k.ldes_initsol.empty()) o.
init_sol = k.ldes_initsol;
6000 if (!k.ldes_rest_url.empty()) o.
rest_url = k.ldes_rest_url;
6013inline std::string ldes_document(
const std::string& file) {
6014 return file.empty() ? stdin_model_text() :
line::ldes::detail::read_file(file);
6019 return i < M.
rows() && j < M.
cols() ? M(i, j) : 0.0;
6033 const std::vector<std::string>& extra) {
6037 "SolverLDES exceeded its wall-clock budget (--ldes-maxtime) and was terminated before "
6038 "it wrote a result; raise the budget or lower --samples");
6041 "SolverLDES: the engine reported no station names, so its metrics cannot be labelled; "
6042 "the run produced no result document");
6056 std::printf(
"SolverLDES arith=double method=%s type=%s engine=%s samples=%zu seed=%ld "
6057 "time=%.6g events=%lld stopping=%s\n",
6087int solve_model_ldes_avg(
const std::string& file,
const Knobs& k) {
6090 if (!g_json_output) ldes_banner(r, o);
6097 if (!r.
QNCI.
empty()) ci[
"QNCI"] = matrix_json<double>(r.
QNCI);
6098 if (!r.
UNCI.
empty()) ci[
"UNCI"] = matrix_json<double>(r.
UNCI);
6099 if (!r.
RNCI.
empty()) ci[
"RNCI"] = matrix_json<double>(r.
RNCI);
6100 if (!r.
TNCI.
empty()) ci[
"TNCI"] = matrix_json<double>(r.
TNCI);
6101 if (!r.
ANCI.
empty()) ci[
"ANCI"] = matrix_json<double>(r.
ANCI);
6102 if (!r.
WNCI.
empty()) ci[
"WNCI"] = matrix_json<double>(r.
WNCI);
6103 if (!ci.empty()) extra[
"CI"] = ci;
6107 f[
"QNfcr"] = matrix_json<double>(r.
QNfcr);
6108 f[
"RNfcr"] = matrix_json<double>(r.
RNfcr);
6109 f[
"TNfcr"] = matrix_json<double>(r.
TNfcr);
6110 f[
"WNfcr"] = matrix_json<double>(r.
WNfcr);
6119 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
6123 if (!it->second.hit.empty()) c[
"hit"] = matrix_json<double>(it->second.hit);
6124 if (!it->second.delayed.empty()) c[
"delayed"] = matrix_json<double>(it->second.delayed);
6125 if (!it->second.miss.empty()) c[
"miss"] = matrix_json<double>(it->second.miss);
6126 if (!it->second.latency.empty()) c[
"latency"] = matrix_json<double>(it->second.latency);
6127 if (!it->second.hitList.empty()) c[
"hitList"] = matrix_json<double>(it->second.hitList);
6128 if (!it->second.itemProb.empty())
6129 c[
"itemProb"] = matrix_json<double>(it->second.itemProb);
6130 if (!it->second.listCost.empty())
6131 c[
"listCost"] = matrix_json<double>(it->second.listCost);
6134 extra[
"cacheMetrics"] = cm;
6155 for (std::size_t j = 0; j < sn.
nstations; ++j)
6157 std::vector<std::size_t> cl_of(r.
class_names.size(), 0);
6158 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6159 for (std::size_t k = 0; k < sn.
nclasses; ++k)
6163 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6164 if (st_of[i] && cl_of[c]) RNs(st_of[i] - 1, cl_of[c] - 1) = ldes_at(r.
RN, i, c);
6167 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6168 WNd(i, c) = (st_of[i] && cl_of[c]) ? WNs(st_of[i] - 1, cl_of[c] - 1)
6169 : ldes_at(r.
WN, i, c);
6173 [&](std::size_t i, std::size_t c) {
6175 v.q = ldes_at(r.QN, i, c);
6176 v.u = ldes_at(r.UN, i, c);
6177 v.r = ldes_at(r.RN, i, c);
6179 v.a = ldes_at(r.AN, i, c);
6180 v.t = ldes_at(r.TN, i, c);
6183 extra, ldes_envelope(r, o));
6186 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s\n",
"Region",
"JobClass",
"QLen",
"RespT",
6187 "Tput",
"Weight",
"MemOcc");
6188 for (std::size_t i = 0; i < r.
QNfcr.
rows(); ++i)
6189 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6190 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
6191 (
"Region" + std::to_string(i + 1)).c_str(), r.
class_names[c].c_str(),
6192 ldes_at(r.
QNfcr, i, c), ldes_at(r.
RNfcr, i, c),
6197 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Cache",
"JobClass",
"Hit",
"Delayed",
6199 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
6202 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6203 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", it->first.c_str(),
6204 r.
class_names[c].c_str(), ldes_at(it->second.hit, 0, c),
6205 ldes_at(it->second.delayed, 0, c), ldes_at(it->second.miss, 0, c),
6206 ldes_at(it->second.latency, 0, c));
6225int solve_model_ldes_tran(
const std::string& file,
const Knobs& k) {
6230 std::vector<std::string> extra;
6231 extra.push_back(
"--trajectory");
6233 if (r.
t.empty() || r.
QNt.empty())
6235 "SolverLDES -a tran produced no trajectory: the engine ran but recorded no bucket over "
6236 "[" + line::ldes::detail::shortest(k.t0) +
"," +
6237 line::ldes::detail::shortest(k.t1) +
"]");
6239 if (g_json_output) {
6241 p[
"type"] =
"TranAvgTable";
6246 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
6247 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
6250 if (r.
QNt[i][c].empty())
continue;
6253 :
"Station" + std::to_string(i);
6258 line::reg::Json tt = line::reg::Json::array(), q = line::reg::Json::array(),
6259 u = line::reg::Json::array(), x = line::reg::Json::array();
6260 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
6261 tt.push_back(r.
QNt[i][c](j, 1));
6262 q.push_back(r.
QNt[i][c](j, 0));
6264 if (i < r.
UNt.size() && c < r.
UNt[i].size())
6265 for (std::size_t j = 0; j < r.
UNt[i][c].rows(); ++j)
6266 u.push_back(r.
UNt[i][c](j, 0));
6267 if (i < r.
TNt.size() && c < r.
TNt[i].size())
6268 for (std::size_t j = 0; j < r.
TNt[i][c].rows(); ++j)
6269 x.push_back(r.
TNt[i][c](j, 0));
6274 curves.push_back(e);
6276 p[
"curves"] = curves;
6277 p[
"tset"] = vector_json(r.
t);
6282 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6283 p[it.key()] = it.value();
6284 emit_analysis<double>(
"tran", p, r.
method);
6288 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
6290 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
6291 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
6292 if (r.
QNt[i][c].empty())
continue;
6293 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
6294 const bool hu = i < r.
UNt.size() && c < r.
UNt[i].size() &&
6295 j < r.
UNt[i][c].rows();
6296 const bool hx = i < r.
TNt.size() && c < r.
TNt[i].size() &&
6297 j < r.
TNt[i][c].rows();
6298 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
6301 r.
QNt[i][c](j, 1), r.
QNt[i][c](j, 0),
6302 hu ? r.
UNt[i][c](j, 0) : 0.0, hx ? r.
TNt[i][c](j, 0) : 0.0);
6321int solve_model_ldes_cdf(
const std::string& file,
const Knobs& k,
const char* key,
6324 std::vector<std::string> extra;
6325 extra.push_back(
"--respt-samples");
6329 "SolverLDES -a cdf needs the per-job response times the engine records under "
6330 "--respt-samples and the run returned none; raise --samples so completions are "
6335 std::vector<std::vector<std::vector<double>>> tt(r.
respTimeSamples.size()), ff(
6342 if (x.empty())
continue;
6343 std::sort(x.begin(), x.end());
6344 const double n =
static_cast<double>(x.size());
6345 for (std::size_t j = 0; j < x.size(); ++j) {
6346 if (j + 1 < x.size() && x[j + 1] == x[j])
continue;
6347 tt[i][c].push_back(x[j]);
6348 ff[i][c].push_back(
static_cast<double>(j + 1) / n);
6353 if (g_json_output) {
6357 p[
"algorithm"] =
"empirical";
6359 for (std::size_t i = 0; i < tt.size(); ++i)
6360 for (std::size_t c = 0; c < tt[i].size(); ++c) {
6361 if (tt[i][c].empty())
continue;
6364 :
"Station" + std::to_string(i);
6369 e[
"t"] = vector_json(tt[i][c]);
6370 e[
"F"] = vector_json(ff[i][c]);
6375 emit_analysis<double>(key, p, std::string());
6379 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
6380 for (std::size_t i = 0; i < tt.size(); ++i)
6381 for (std::size_t c = 0; c < tt[i].size(); ++c)
6382 for (std::size_t j = 0; j < tt[i][c].size(); ++j)
6383 std::printf(
"%-16s %-14s %14.8g %14.10g\n",
6386 tt[i][c][j], ff[i][c][j]);
6403int solve_model_ldes_sample(
const std::string& file,
const Knobs& k) {
6408 std::vector<std::string> extra;
6409 extra.push_back(
"--trajectory");
6411 if (r.
t.empty() || r.
QNt.empty())
6413 "SolverLDES -a sample produced no trajectory over [0," +
6414 line::ldes::detail::shortest(o.
t1) +
"]");
6416 const std::size_t M = r.
QNt.size(), K = r.
class_names.size(), n = r.
t.size();
6417 if (g_json_output) {
6419 p[
"type"] =
"SamplePath";
6421 p[
"scope"] =
"(system)";
6423 p[
"t"] = vector_json(r.
t);
6425 for (std::size_t j = 0; j < n; ++j) {
6427 for (std::size_t i = 0; i < M; ++i)
6428 for (std::size_t c = 0; c < K; ++c)
6429 row.push_back(c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
6436 for (std::size_t i = 0; i < M; ++i)
6437 for (std::size_t c = 0; c < K; ++c)
6440 p[
"columns"] = cols;
6445 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6446 p[it.key()] = it.value();
6447 emit_analysis<double>(
"sample", p, r.
method);
6451 std::printf(
"%14s %s\n",
"Time",
"SysState (station-major, per class)");
6452 for (std::size_t j = 0; j < n; ++j) {
6453 std::printf(
"%14.8g ", r.
t[j]);
6454 for (std::size_t i = 0; i < M; ++i)
6455 for (std::size_t c = 0; c < K; ++c)
6456 std::printf(
" %g", c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
6478int solve_model_ldes_reward(
const std::string& file,
const Knobs& k) {
6483 "-s ldes -a reward needs a reward declared on the model (set_reward(name, fn), the "
6484 "`rewards` block of model.json); there is nothing to average");
6487 std::vector<std::string> extra;
6488 extra.push_back(
"--export-histogram");
6492 "SolverLDES -a reward needs the joint-state residence-time histogram the engine "
6493 "exports under --export-histogram and the run returned none");
6500 "SolverLDES -a reward: the state histogram carries no residence time, so no state "
6501 "distribution can be formed from it");
6504 std::vector<double> E(sn.
reward.size(), 0.0);
6505 std::vector<std::string> names(sn.
reward.size());
6506 for (std::size_t l = 0; l < sn.
reward.size(); ++l) {
6507 names[l] = sn.
reward[l].name;
6508 for (std::size_t s = 0; s < ns; ++s) {
6509 std::vector<double> row(w);
6514 E[l] += (t / total) * sn.
reward[l].fn(row);
6518 if (g_json_output) {
6520 p[
"type"] =
"AvgReward";
6522 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
6524 p[
"E"] = vector_json(E);
6530 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6531 p[it.key()] = it.value();
6534 emit_analysis<double>(
"reward", p, std::string());
6538 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
6539 for (std::size_t l = 0; l < E.size(); ++l)
6540 std::printf(
"%-28s %16.10g\n", names[l].c_str(), E[l]);
6563int solve_model_ldes_prob(
const std::string& file,
const Knobs& k) {
6569 if (!k.state.empty()) {
6570 std::size_t ist = 0;
6571 if (k.node && k.node <= sn.
nodes.size()) ist = sn.
nodes[k.node - 1].station;
6574 " is not a station; the LDES state histogram records station "
6575 "queue lengths only");
6576 if (k.state.size() != R)
6577 throw line::InputError(
"-s ldes -a prob takes --state as one job count per class (" +
6578 std::to_string(R) +
"), got " + std::to_string(k.state.size()));
6579 for (std::size_t r = 0; r < R; ++r) nir(ist - 1, r) =
static_cast<double>(k.state[r]);
6583 std::vector<std::string> extra;
6584 extra.push_back(
"--export-histogram");
6590 "SolverLDES -a prob needs the joint-state residence-time histogram the engine exports "
6591 "under --export-histogram and the run returned none");
6592 if (space.
cols() < M * R)
6594 std::to_string(space.
cols()) +
" columns, fewer than the " +
6595 std::to_string(M * R) +
" of " + std::to_string(M) +
6596 " stations by " + std::to_string(R) +
" classes");
6598 std::vector<double> dwell(space.
rows(), 0.0);
6600 for (std::size_t s = 0; s < space.
rows() && s < tm.
rows(); ++s)
6601 for (std::size_t c = 0; c < tm.
cols(); ++c) dwell[s] += tm(s, c);
6602 for (
double d : dwell) total += d;
6605 "SolverLDES -a prob: the state histogram carries no residence time, so no state "
6606 "probability can be formed from it");
6609 auto matches = [&](std::size_t s, std::size_t i) {
6610 for (std::size_t r = 0; r < R; ++r)
6611 if (!(std::abs(space(s, i * R + r) - nir(i, r)) < 1e-9))
return false;
6614 std::vector<double> prob(M, 0.0);
6615 std::vector<bool> seen(M,
false);
6617 bool sys_seen =
false;
6618 for (std::size_t s = 0; s < space.
rows(); ++s) {
6620 for (std::size_t i = 0; i < M; ++i) {
6622 if (matches(s, i)) {
6623 prob[i] += dwell[s];
6634 for (
double& p : prob) p /= total;
6637 if (g_json_output) {
6639 p[
"type"] =
"ProbAggr";
6641 p[
"ProbSys"] = psys;
6642 p[
"ProbSysAggr"] = psys;
6643 p[
"SysStateSeen"] = sys_seen;
6644 p[
"states"] = space.
rows();
6645 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
6646 sv = line::reg::Json::array();
6647 for (std::size_t i = 0; i < M; ++i) {
6649 pa.push_back(prob[i]);
6650 sv.push_back(
static_cast<bool>(seen[i]));
6655 p[
"StateSeen"] = sv;
6656 emit_analysis<double>(
"prob", p, res.
method);
6659 ldes_banner(res, o);
6660 std::printf(
"ProbSys = ProbSysAggr %.10g%s\n", psys, sys_seen ?
"" :
" (state never observed)");
6661 std::printf(
"%-16s %14s\n",
"Station",
"Prob=ProbAggr");
6662 for (std::size_t i = 0; i < M; ++i)
6663 std::printf(
"%-16s %14.10g%s\n", sn.
stations[i].name.c_str(), prob[i],
6664 seen[i] ?
"" :
" (state never observed)");
6674int solve_model_ldes(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
6675 if (analysis ==
"avg")
return solve_model_ldes_avg(file, k);
6676 if (analysis ==
"tran")
return solve_model_ldes_tran(file, k);
6684 if (analysis ==
"cdf")
return solve_model_ldes_cdf(file, k,
"cdf",
"CdfRespT");
6685 if (analysis ==
"cdfpasst")
return solve_model_ldes_cdf(file, k,
"cdfpasst",
"CdfPassT");
6686 if (analysis ==
"trancdf")
return solve_model_ldes_cdf(file, k,
"trancdf",
"TranCdfRespT");
6687 if (analysis ==
"trancdfpasst")
6688 return solve_model_ldes_cdf(file, k,
"trancdfpasst",
"TranCdfPassT");
6689 if (analysis ==
"sample")
return solve_model_ldes_sample(file, k);
6690 if (analysis ==
"reward")
return solve_model_ldes_reward(file, k);
6691 if (analysis ==
"prob")
return solve_model_ldes_prob(file, k);
6693 "SolverLDES ports -a avg (getAvg) and its four views -a node, -a sys, -a chain and "
6694 "-a nodechain, -a tran (getTranAvg), -a cdf / cdf-passt / "
6695 "tran-cdf-respt / tran-cdf-passt (the empirical passage law, one measurement under the "
6696 "four names the reference gives it), -a sample (sampleSys), -a reward "
6697 "(getAvgReward) and -a prob (getProb, getProbAggr, getProbSys, getProbSysAggr); got '" +
6701std::string auto_getter_of_analysis(
const std::string& analysis) {
6702 if (analysis ==
"prob")
return "getProbSysAggr";
6703 if (analysis ==
"marg")
return "getProbMarg";
6704 if (analysis ==
"sysmarg")
return "getProbSysMarg";
6705 if (analysis ==
"normconst")
return "getProbNormConstAggr";
6706 if (analysis ==
"tranprob")
return "getTranProbSysAggr";
6707 if (analysis ==
"sample")
return "sampleSys";
6708 if (analysis ==
"cdf")
return "getCdfRespT";
6709 if (analysis ==
"gen")
return "getInfGen";
6710 if (analysis ==
"states")
return "getStateSpace";
6711 if (analysis ==
"reward")
return "getAvgReward";
6712 if (analysis ==
"sens")
return "getSensitivityTable";
6713 if (analysis ==
"tran")
return "getTranAvg";
6714 if (analysis ==
"internals")
return "getMAMResult";
6720 if (analysis ==
"node")
return "getAvgNodeTable";
6721 return "getAvgTable";
6729 std::vector<std::string> order;
6735std::string auto_cli_token_of_family(
const std::string& fam) {
6736 if (fam ==
"mva" || fam ==
"nc" || fam ==
"ctmc" || fam ==
"mam" || fam ==
"ag" ||
6737 fam ==
"ssa" || fam ==
"ba" || fam ==
"uq" || fam ==
"env")
6739 if (fam ==
"fld")
return "fluid";
6740 if (fam ==
"ldes") {
6746 "--method ldes names the discrete-event engine, and no engine was found beside "
6747 "this binary (common/ldes or common/ldes.jar, or $LINE_LDES_DIR); -s ssa is the "
6748 "simulator this port builds in process");
6762 if (fam ==
"lqns")
return "lqns";
6765 "--method ln names the layered solver, which takes a LayeredNetwork: pass the model "
6766 "as -i lqnx -s ln rather than as a Network");
6767 throw line::InputError(
"SolverAUTO: no engine stands behind method family '" + fam +
"'");
6778bool model_is_environment(
const std::string& file) {
6780 line::io::detail::json root;
6782 std::istringstream in(stdin_model_text());
6785 std::ifstream in(file.c_str());
6786 if (!in)
return false;
6792 if (!root.is_object())
return false;
6793 const line::io::detail::json& model = root.contains(
"model") ? root.at(
"model") : root;
6794 return model.is_object() && model.value(
"type", std::string()) ==
"Environment";
6820AutoPlan choose_auto_plan(
const std::string& file,
const std::string& analysis,
6821 const std::string& method_token) {
6825 plan.order.push_back(auto_cli_token_of_family(tok.
family));
6830 const std::string getter = auto_getter_of_analysis(analysis);
6831 if (model_is_environment(file)) {
6834 plan.order.push_back(
"env");
6835 for (std::size_t i = 0; i < ec.
skipped.size(); ++i)
6836 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
6838 if (!ec.
skipped.empty()) plan.note +=
", which this port does not build)";
6846 const std::vector<line::autosolver::AutoSolver> proposed =
6848 for (std::size_t i = 0; i < proposed.size(); ++i)
6850 for (std::size_t i = 0; i < c.
skipped.size(); ++i)
6851 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
6853 if (!c.
skipped.empty()) plan.note +=
", which this port does not build)";
6857AutoPlan choose_auto_plan_dispatch(
const std::string& arith,
const std::string& file,
6858 const std::string& analysis,
const std::string& method_token) {
6859 if (arith ==
"exact")
return choose_auto_plan<line::Rational>(file, analysis, method_token);
6860 if (arith ==
"real:16")
return choose_auto_plan<line::Real<16> >(file, analysis, method_token);
6861 if (arith ==
"real" || arith ==
"real:32")
6862 return choose_auto_plan<line::Real<32> >(file, analysis, method_token);
6863 if (arith ==
"real:64")
return choose_auto_plan<line::Real<64> >(file, analysis, method_token);
6864 if (arith ==
"real:128")
6865 return choose_auto_plan<line::Real<128> >(file, analysis, method_token);
6866 if (arith ==
"real:256")
6867 return choose_auto_plan<line::Real<256> >(file, analysis, method_token);
6868 return choose_auto_plan<double>(file, analysis, method_token);
6882double ln_sanitize(
double x) {
6883 const double r = std::round(x * 10.0);
6890 switch (l.
type[i]) {
6894 default:
return "Activity";
6910 using namespace line;
6911 const std::vector<qn::Layer<T> >& ens = solver.
layers();
6912 for (std::size_t k = 0; k < ens.size(); ++k) {
6914 std::printf(
"LAYER %zu %s nstations=%zu nclasses=%zu nchains=%zu\n", k + 1, L.
name.c_str(),
6916 for (std::size_t i = 0; i < L.
stations.size(); ++i)
6917 std::printf(
" STATION %zu %s sched=%s nservers=%g\n", i + 1, L.
stations[i].name.c_str(),
6919 for (std::size_t r = 0; r < L.
classes.size(); ++r)
6920 std::printf(
" CLASS %zu %s pop=%.17g refstat=%zu completes=%d\n", r + 1,
6923 for (std::size_t i = 0; i < L.
stations.size(); ++i)
6924 for (std::size_t r = 0; r < L.
classes.size(); ++r) {
6926 std::printf(
" RATE %s %s %.17g scv=%.17g\n", L.
stations[i].name.c_str(),
6930 for (
const auto& kv : L.
P) {
6932 for (std::size_t i = 0; i < B.
rows(); ++i)
6933 for (std::size_t j = 0; j < B.
cols(); ++j) {
6935 if (p == 0.0)
continue;
6936 std::printf(
" ROUTE %s->%s %s->%s %.17g\n",
6937 L.
classes[kv.first.first - 1].name.c_str(),
6938 L.
classes[kv.first.second - 1].name.c_str(),
6939 L.
nodes[i].name.c_str(), L.
nodes[j].name.c_str(), p);
6958 using namespace line;
6963 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
6964 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
6965 if (k.no_interlocking)
opt.interlocking =
false;
6966 if (!k.interlock_method.empty())
opt.interlock_method = k.interlock_method;
6967 if (k.interlock_maxpaths >= 0.0)
opt.interlock_maxpaths = k.interlock_maxpaths;
6968 if (!k.interlock_refpath_scope.empty())
opt.interlock_refpath_scope = k.interlock_refpath_scope;
6969 if (!k.layer_solver.empty())
opt.layer_solver = k.layer_solver;
6970 if (!k.method.empty())
opt.method = k.method;
6971 if (k.samples)
opt.layer_ssa.samples = k.samples;
6972 if (k.seed)
opt.layer_ssa.seed = k.seed;
6973 if (k.t1 >= 0.0)
opt.timespan_end = k.t1;
6974 if (k.tran_points)
opt.tran_points = k.tran_points;
6975 if (!k.ln_transient.empty())
opt.ln_transient = k.ln_transient;
6976 if (!k.ln_transient_channels.empty())
opt.ln_transient_channels = k.ln_transient_channels;
6981inline const char* ln_layer_engine_name(
const std::string& layer_solver) {
6982 if (layer_solver ==
"fluid")
return "Fluid";
6983 if (layer_solver ==
"nc")
return "NC";
6984 if (layer_solver ==
"ssa")
return "SSA";
6996int run_ln_tran(
const std::string& file,
const std::string& output,
const Knobs& k) {
6997 using namespace line;
7002 const std::vector<qn::Layer<T>>& layers = solver.
layers();
7004 if (output ==
"json") {
7008 j[
"mode"] =
tr.mode;
7009 j[
"iterations"] =
tr.iterations;
7010 j[
"gap"] = ln_sanitize(
tr.gap);
7012 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
7014 le[
"layer"] = layers[e].name;
7016 for (
double t :
tr.layers[e].t) tt.push_back(ln_sanitize(t));
7019 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
7020 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
7022 s[
"station"] = layers[e].stations[i].name;
7023 s[
"class"] = layers[e].classes[r].name;
7024 auto arr = [&](
const std::vector<double>& v) {
7026 for (
double x : v) a.push_back(ln_sanitize(x));
7029 s[
"QLen"] = arr(
tr.layers[e].QN[i][r]);
7030 s[
"Util"] = arr(
tr.layers[e].UN[i][r]);
7031 s[
"Tput"] = arr(
tr.layers[e].TN[i][r]);
7032 series.push_back(s);
7034 le[
"series"] = series;
7038 emit_document(dump_document(j, 1));
7042 std::printf(
"SolverLN(Solver%s) getTranAvg arith=%s mode=%s layers=%zu iterations=%ld gap=%.3e\n",
7044 tr.layers.size(),
tr.iterations,
tr.gap);
7045 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
7046 const std::vector<double>& t =
tr.layers[e].t;
7047 if (t.empty())
continue;
7048 std::printf(
"\nLayer %s (%zu points on [%.6g, %.6g])\n", layers[e].name.c_str(), t.size(),
7049 t.front(), t.back());
7050 std::printf(
"%-30s %-24s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen(0)",
7051 "QLen(end)",
"Util(end)",
"Tput(end)");
7052 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
7053 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
7054 const std::vector<double>& q =
tr.layers[e].QN[i][r];
7055 if (q.empty())
continue;
7056 std::printf(
"%-30s %-24s %12.6g %12.6g %12.6g %12.6g\n",
7057 layers[e].stations[i].name.c_str(), layers[e].classes[r].name.c_str(),
7058 ln_sanitize(q.front()), ln_sanitize(q.back()),
7059 ln_sanitize(
tr.layers[e].UN[i][r].back()),
7060 ln_sanitize(
tr.layers[e].TN[i][r].back()));
7068int run_ln_sens(
const std::string& file,
const std::string& output,
const Knobs& k) {
7069 using namespace line;
7074 if (!k.sens_method.empty()) so.
method = k.sens_method;
7075 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
7076 if (k.sens_step > 0.0) so.
step = k.sens_step;
7079 if (output ==
"json") {
7083 j[
"method"] = tbl.
method;
7085 for (
const auto& r : tbl.
rows) {
7087 o[
"Layer"] = r.layer;
7088 o[
"Station"] = r.station;
7089 o[
"JobClass"] = r.jobclass;
7097 emit_document(dump_document(j, 1));
7101 std::printf(
"SolverLN(Solver%s) getSensitivityTable arith=%s method=%s rows=%zu\n",
7104 std::printf(
"%-28s %-28s %-20s %14s %14s %14s %14s\n",
"Layer",
"Station",
"JobClass",
7105 "dTput_dRate",
"dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
7106 for (
const auto& r : tbl.
rows)
7107 std::printf(
"%-28s %-28s %-20s %14.6g %14.6g %14.6g %14.6g\n", r.layer.c_str(),
7108 r.station.c_str(), r.jobclass.c_str(),
7118int run_ln_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
7119 using namespace line;
7125 opt.method =
"moment3";
7130 if (output ==
"json") {
7135 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
7137 o[
"entry"] = names.names[model.
eshift + e];
7138 reg::Json tt = reg::Json::array(), ff = reg::Json::array();
7139 for (std::size_t p = 0; p < cdf[e].t.size(); ++p) {
7140 tt.push_back(ln_sanitize(cdf[e].t[p]));
7141 ff.push_back(ln_sanitize(cdf[e].cdf[p]));
7147 j[
"entries"] = rows;
7148 emit_document(dump_document(j, 1));
7152 std::printf(
"SolverLN(Solver%s) getCdfRespT arith=%s method=moment3 entries=%zu\n",
7154 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
7155 if (cdf[e].t.empty()) {
7156 std::printf(
"%-40s (no distribution: the entry has no fitted term)\n",
7157 names.names[model.
eshift + e].c_str());
7161 auto quantile = [&](
double p) {
7162 for (std::size_t i = 0; i < cdf[e].cdf.size(); ++i)
7163 if (cdf[e].cdf[i] >= p)
return cdf[e].t[i];
7164 return cdf[e].t.back();
7166 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g points=%zu\n",
7167 names.names[model.
eshift + e].c_str(), ln_sanitize(quantile(0.25)),
7168 ln_sanitize(quantile(0.50)), ln_sanitize(quantile(0.75)),
7169 ln_sanitize(quantile(0.95)), cdf[e].t.size());
7175int run_ln(
const std::string& file,
const std::string& output,
const Knobs& k) {
7176 using namespace line;
7180 const int repeat = k.repeat > 0 ? k.repeat : 1;
7182 double best = 1e300;
7184 std::size_t nlayers = 0;
7185 for (
int rep = 0; rep < repeat; ++rep) {
7186 const auto t0 = std::chrono::steady_clock::now();
7190 if (output ==
"layers") {
7191 ln_dump_layers(solver);
7194 const auto t1 = std::chrono::steady_clock::now();
7195 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
7201 if (output ==
"json") {
7205 j[
"layers"] = nlayers;
7208 j[
"seconds"] = best;
7210 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7212 r[
"node"] = names.names[i];
7213 r[
"type"] = ln_element_kind(names, i);
7214 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
7216 else if (sol.
is_bound) r[key] = 0.0;
7217 else r[key] =
nullptr;
7227 emit_document(dump_document(j, 1));
7235 "SolverLN(Solver%s) arith=%s type=%s layers=%zu iterations=%d converged=%d time=%.4fs\n",
7239 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7240 "RespT",
"ResidT",
"Tput");
7241 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7254 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
7255 if (!d[i] && sol.
is_bound) std::snprintf(buf, 24,
"%12.6g", 0.0);
7256 else if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
7259 char q[24], u[24], rr[24], w[24], t[24];
7265 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
7266 ln_element_kind(names, i), q, u, rr, w, t);
7314int run_ln_ldes_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
7315 using namespace line;
7318 if (k.samples) o.
samples = k.samples;
7319 if (k.seed) o.
seed =
static_cast<long>(k.seed);
7325 const std::vector<ldes::engine::LnEntryCdf> cdf =
7328 for (std::size_t e = 0; e < model.
nentries; ++e) {
7333 if (output ==
"json") {
7336 j[
"engine"] =
"native-ln";
7338 for (std::size_t e = 0; e < model.
nentries; ++e) {
7341 reg::Json at = reg::Json::array(), af = reg::Json::array();
7342 for (std::size_t i = 0; i < tt[e].size(); ++i) {
7343 at.push_back(tt[e][i]);
7344 af.push_back(ff[e][i]);
7348 ob[
"observations"] =
static_cast<double>(
7352 j[
"entries"] = rows;
7353 emit_document(dump_document(j, 1));
7357 std::printf(
"SolverLDES(native LN engine) getCdfRespT entries=%zu\n", model.
nentries);
7358 for (std::size_t e = 0; e < model.
nentries; ++e) {
7359 if (tt[e].empty()) {
7360 std::printf(
"%-40s (no observation)\n", model.
names[model.
eshift + e + 1].c_str());
7365 const std::vector<double>& t;
7366 const std::vector<double>& f;
7367 double operator()(
double p)
const {
7368 for (std::size_t i = 0; i < f.size(); ++i)
7369 if (f[i] >= p)
return t[i];
7372 } q = {tt[e], ff[e]};
7373 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g n=%zu\n",
7374 model.
names[model.
eshift + e + 1].c_str(), q(0.25), q(0.50), q(0.75),
7380int run_ln_ldes(
const std::string& file,
const std::string& output,
const Knobs& k) {
7381 using namespace line;
7388 if (k.samples) o.
samples = k.samples;
7389 if (k.seed) o.
seed =
static_cast<long>(k.seed);
7396 const int repeat = k.repeat > 0 ? k.repeat : 1;
7397 double best = 1e300;
7399 for (
int rep = 0; rep < repeat; ++rep) {
7400 const auto t0 = std::chrono::steady_clock::now();
7402 const auto t1 = std::chrono::steady_clock::now();
7403 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
7411 const auto def = [&](std::size_t i, Col c) {
7415 if (output ==
"json") {
7418 j[
"arith"] =
"double";
7419 j[
"solver"] =
"ldes";
7424 j[
"engine"] =
"native-ln";
7429 j[
"seconds"] = best;
7431 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7433 row[
"node"] = model.
names[i];
7434 row[
"type"] = ln_element_kind(model, i);
7435 auto put = [&](
const char* key,
double v,
bool defined) {
7436 if (defined) row[key] = v;
7437 else row[key] =
nullptr;
7439 put(
"QLen", r.
QLN(i, 0), def(i, Col::QLen));
7440 put(
"Util", r.
ULN(i, 0), def(i, Col::Util));
7441 put(
"RespT", r.
RLN(i, 0), def(i, Col::RespT));
7442 put(
"ResidT", r.
WLN(i, 0), def(i, Col::ResidT));
7443 put(
"Tput", r.
TLN(i, 0), def(i, Col::Tput));
7444 rows.push_back(row);
7447 emit_document(dump_document(j, 1));
7451 std::printf(
"SolverLDES(native LN engine) arith=double type=%s samples=%zu seed=%ld "
7452 "simtime=%.6g completions=%lld time=%.4fs\n",
7455 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7456 "RespT",
"ResidT",
"Tput");
7457 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7459 auto fmt = [&](
double v,
bool defined,
char* buf) {
7460 if (!defined) std::snprintf(buf, 24,
"%12s",
"NaN");
7461 else std::snprintf(buf, 24,
"%12.6g", v);
7463 char q[24], u[24], rr[24], w[24], t[24];
7464 fmt(r.
QLN(i, 0), def(i, Col::QLen), q);
7465 fmt(r.
ULN(i, 0), def(i, Col::Util), u);
7466 fmt(r.
RLN(i, 0), def(i, Col::RespT), rr);
7467 fmt(r.
WLN(i, 0), def(i, Col::ResidT), w);
7468 fmt(r.
TLN(i, 0), def(i, Col::Tput), t);
7469 std::printf(
"%-62s %-10s %s %s %s %s %s\n", model.
names[i].c_str(),
7470 ln_element_kind(model, i), q, u, rr, w, t);
7487int run_lqns(
const std::string& file,
const std::string& output,
const Knobs& k) {
7488 using namespace line;
7492 if (!k.method.empty())
opt.method = k.method;
7493 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
7494 if (k.samples)
opt.samples =
static_cast<double>(k.samples);
7495 opt.verbose = k.verbose;
7497 opt.remote = k.remote;
7498 if (!k.remote_url.empty())
opt.remote_url = k.remote_url;
7499 opt.timeout_seconds = k.timeout_seconds;
7506 if (output ==
"json") {
7511 j[
"method"] =
opt.method;
7513 j[
"seconds"] = solver.runtime();
7515 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
7517 r[
"node"] = names.names[i];
7518 r[
"type"] = ln_element_kind(names, i);
7519 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
7521 else r[key] =
nullptr;
7531 emit_document(dump_document(j, 1));
7535 std::printf(
"SolverLQNS(%s) arith=%s type=%s iterations=%d time=%.4fs\n",
7539 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7540 "RespT",
"ResidT",
"Tput");
7541 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
7543 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
7544 if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
7546 std::snprintf(buf, 24,
"%12.6g",
7549 char q[24], u[24], rr[24], w[24], t[24];
7555 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
7556 ln_element_kind(names, i), q, u, rr, w, t);
7582std::string upper_tag(
const std::string& s) {
7583 std::string out = s;
7584 for (std::size_t i = 0; i < out.size(); ++i)
7585 out[i] =
static_cast<char>(std::toupper(
static_cast<unsigned char>(out[i])));
7589int solve_lqn_dispatch(
const std::string& arith,
const std::string& solver,
7590 const std::string& analysis,
const std::string& output,
7591 const std::string& file,
const Knobs& k) {
7594 "a layered model is read from a file: pass -f <model.lqnx> or -f <model.json> (neither "
7595 "layered reader has a stdin form)");
7596 if (analysis !=
"avg" && analysis !=
"tran" && analysis !=
"sens" && analysis !=
"cdf")
7598 "the layered path ports -a avg (getAvgTable), -a tran (getTranAvg), -a sens "
7599 "(getSensitivityTable) and -a cdf (getCdfRespT); got '" + analysis +
"'");
7600 const std::string s = solver.empty() ?
"auto" : solver;
7601 if (s !=
"auto" && s !=
"ln" && s !=
"ln.mva" && s !=
"ln.comom" && s !=
"lqns" &&
7604 "the layered path takes -s ln, ln.mva, ln.comom, ldes, lqns or auto (got '" + s +
7605 "'); a Network solver cannot be applied to a LayeredNetwork directly");
7614 std::string engine = s;
7618 if (s ==
"auto" && kk.layer_solver.empty()) {
7619 std::string getter =
"getAvgTable";
7620 if (analysis ==
"tran") getter =
"getTranAvg";
7621 else if (analysis ==
"cdf") getter =
"getCdfRespT";
7622 else if (analysis ==
"sens") getter =
"getSensitivityTable";
7626 bool has_cache_task =
false;
7627 for (std::size_t i = 0; i < probe.
iscache.size(); ++i)
7628 if (probe.
iscache[i]) has_cache_task =
true;
7632 if (token ==
"lqns") engine =
"lqns";
7633 else if (token ==
"ln.comom") kk.layer_solver =
"nc";
7634 else if (token ==
"ln.fld") kk.layer_solver =
"fluid";
7635 else kk.layer_solver =
"mva";
7637 for (std::size_t i = 0; i < lc.
skipped.size(); ++i)
7638 note += std::string(i ?
", " :
" (the ranking preferred ") +
7640 if (!lc.
skipped.empty()) note +=
", which is not available here)";
7641 std::printf(
"SolverAUTO selected %s%s\n", token.c_str(), note.c_str());
7649 if (engine ==
"lqns") {
7650 if (analysis !=
"avg")
7652 "SolverLQNS reports the mean table its binary computes; it has no transient, no "
7653 "sensitivity and no response-time distribution here, so it takes -a avg (got '" +
7655 if (!kk.layer_solver.empty())
7657 "--layer-solver names the engine SolverLN runs on each layer; -s lqns solves no "
7658 "layers, it hands the whole model to the lqns binary");
7659 if (output ==
"layers")
7661 "-o layers dumps the stations and routing SolverLN BUILT from the model; lqns "
7662 "builds its own submodels inside another process and this port never sees them");
7663 if (kk.iter_tol >= 0.0 || kk.iter_max > 0)
7665 "--iter_tol and --iter_max are SolverLN's layer-iteration knobs; lqns runs its own "
7666 "iteration and takes neither (its --iteration-limit is unreliable as of 6.2.27, "
7667 "which is why the reference stopped passing it)");
7670 "--seed sets the stream of a simulator this port drives; lqsim seeds itself and "
7671 "the wrapper passes no seed, exactly as the reference does not");
7674 "--repeat times a solve by re-running it; re-running lqsim would report a "
7675 "different answer under the same banner");
7676 if (kk.no_interlocking || kk.interlock_knobs_given() || !kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
7677 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0)
7679 "--no-interlocking, --interlock-*, --ln-transient*, and --sens-* are SolverLN options; -s lqns "
7680 "has none of them");
7681 if (arith !=
"double")
7683 "SolverLQNS reads a result file another program wrote in decimal double "
7684 "precision; there is no higher precision to carry, so rerun with --arith double "
7685 "(got '" + arith +
"')");
7692 if (kk.has_cutoff())
7694 "--cutoff bounds the open population of a CTMC state space and applies to -s "
7695 "ctmc; the layered path enumerates no states");
7698 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7699 "reports every LQN element");
7700 return run_lqns<double>(file, output, kk);
7702 if (k.keep || k.verbose || k.remote || !k.remote_url.empty() || k.timeout_seconds)
7704 "--keep, --verbose, --remote, --remote-url and --timeout describe the child process "
7705 "of an external solver and apply to -s lqns only");
7712 if (engine ==
"ldes") {
7713 if (analysis !=
"avg" && analysis !=
"cdf")
7715 "the native LN engine measures a sample path: it takes -a avg for the mean table "
7716 "and -a cdf for the per-entry response time distribution, and has no transient "
7717 "and no sensitivity here (got '" + analysis +
"')");
7718 if (arith !=
"double")
7720 "the native LN engine accumulates its estimators in double, so there is no higher "
7721 "precision to carry; rerun with --arith double (got '" + arith +
"')");
7722 if (output ==
"layers")
7724 "-o layers dumps the stations and routing SolverLN BUILT from the model; -s ldes "
7725 "simulates the layered semantics directly and builds no submodels");
7726 if (!kk.layer_solver.empty())
7728 "--layer-solver names the engine SolverLN runs on each layer; -s ldes solves no "
7729 "layers, it simulates entries, activities and calls directly");
7730 if (kk.iter_tol >= 0.0 || kk.iter_max > 0 || kk.no_interlocking || kk.interlock_knobs_given())
7732 "--iter_tol, --iter_max, --no-interlocking and --interlock-* are SolverLN's layer-iteration "
7733 "knobs; a simulated sample path converges by run length, which is --samples");
7734 if (!kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
7735 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0)
7737 "--ln-transient* and --sens-* are SolverLN options; -s ldes has none of them");
7738 if (!kk.method.empty() && kk.method !=
"default")
7740 "--method on the layered path names the LN UPDATE (default, moment3, mw.*); "
7741 "-s ldes performs no update, it simulates the model (got '" + kk.method +
"')");
7747 if (!kk.ldes_tranfilter.empty() || kk.ldes_warmupfrac >= 0.0 ||
7748 !kk.ldes_cimethod.empty() || kk.ldes_cnvgon || kk.ldes_cnvgtol > 0.0 ||
7749 kk.ldes_slotted || kk.ldes_slotlength > 0.0 || kk.ldes_replications > 0 ||
7750 kk.ldes_numthreads > 0 || kk.ldes_maxtime > 0.0 || !kk.ldes_initsol.empty() ||
7751 !kk.ldes_rest_url.empty())
7753 "the --ldes-* flags configure the SUBPROCESS engine that answers -s ldes on a "
7754 "Network (warmup filter, CI estimator, slot lattice, replications, warm-start "
7755 "placement); the native LN engine behind -i lqnx -s ldes reads --samples and "
7760 if (kk.has_cutoff())
7762 "--cutoff bounds the open population of a CTMC state space and applies to -s "
7763 "ctmc; the layered path enumerates no states");
7766 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7767 "reports every LQN element");
7770 "--tspan sets the horizon of a transient analysis; the native LN engine runs to "
7771 "a completion budget, which is --samples");
7774 "--tol is not an LDES option; the run length is set with --samples");
7775 if (analysis ==
"cdf")
return run_ln_ldes_cdf(file, output, kk);
7776 return run_ln_ldes(file, output, kk);
7780 if (s ==
"ln.comom") {
7781 if (!kk.layer_solver.empty() && kk.layer_solver !=
"nc")
7782 throw line::InputError(
"-s ln.comom already selects NC layers, but --layer-solver says '" +
7783 kk.layer_solver +
"'");
7784 kk.layer_solver =
"nc";
7785 }
else if (s ==
"ln.mva") {
7786 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva")
7787 throw line::InputError(
"-s ln.mva already selects MVA layers, but --layer-solver says '" +
7788 kk.layer_solver +
"'");
7789 kk.layer_solver =
"mva";
7794 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva" && kk.layer_solver !=
"nc" &&
7795 kk.layer_solver !=
"fluid" && kk.layer_solver !=
"ssa")
7796 throw line::InputError(
"--layer-solver takes mva, nc, fluid or ssa (got '" +
7797 kk.layer_solver +
"')");
7799 if ((k.samples || k.seed) && kk.layer_solver !=
"ssa")
7801 "--samples and --seed set the run length and the stream of a SIMULATED layer; the "
7802 "layered path draws no random numbers unless --layer-solver ssa is in force");
7805 "--cutoff bounds the open population of a CTMC state space and applies to -s ctmc; the "
7806 "layered path enumerates no states");
7807 if (k.t1 >= 0.0 && analysis !=
"tran")
7809 "--tspan sets the horizon of a transient analysis and applies to the layered path "
7810 "only with -a tran");
7811 if (analysis ==
"tran" && !(k.t1 >= 0.0))
7813 "-a tran integrates each layer's drift and needs a horizon: pass --tspan <t0>:<t1>");
7816 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7817 "reports every LQN element");
7820 "--tol is not a SolverLN option (LnOptions carries iter_tol and iter_max); "
7825 if (!k.method.empty() && k.method !=
"default" && k.method !=
"moment3" &&
7826 k.method !=
"mw.upper" && k.method !=
"mw.lower")
7828 "--method on the layered path takes default, moment3, mw.upper or mw.lower "
7829 "(got '" + k.method +
"'); the per-layer engine is chosen with --layer-solver");
7830 if ((k.method ==
"mw.upper" || k.method ==
"mw.lower") && analysis !=
"avg")
7832 "--method mw.* reports a throughput and utilization BOUND and solves no layer, so "
7833 "it has no transient, no sensitivity and no response-time law; use -a avg");
7834 if (!k.sens_method.empty() && analysis !=
"sens")
7836 if (!k.ln_transient.empty() && analysis !=
"tran")
7839 if (analysis ==
"tran") {
7840 if (arith !=
"double")
7842 "the layered transient integrates each layer's drift with LSODA, which is double "
7843 "precision by construction; rerun with --arith double (got '" + arith +
"')");
7844 return run_ln_tran<double>(file, output, kk);
7846 if (analysis ==
"cdf") {
7847 if (arith !=
"double")
7849 "-a cdf fits an APH to a fluid passage time, integrated by LSODA in double "
7850 "precision; rerun with --arith double (got '" + arith +
"')");
7851 return run_ln_cdf<double>(file, output, kk);
7853 if (analysis ==
"sens") {
7854 if (arith ==
"double")
return run_ln_sens<double>(file, output, kk);
7855 if (arith ==
"exact")
return run_ln_sens<line::Rational>(file, output, kk);
7856 if (arith ==
"real:16")
return run_ln_sens<line::Real<16> >(file, output, kk);
7857 if (arith ==
"real" || arith ==
"real:32")
7858 return run_ln_sens<line::Real<32> >(file, output, kk);
7859 if (arith ==
"real:64")
return run_ln_sens<line::Real<64> >(file, output, kk);
7860 if (arith ==
"real:128")
return run_ln_sens<line::Real<128> >(file, output, kk);
7861 if (arith ==
"real:256")
return run_ln_sens<line::Real<256> >(file, output, kk);
7862 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7865 if (arith ==
"double")
return run_ln<double>(file, output, kk);
7866 if (arith ==
"exact")
return run_ln<line::Rational>(file, output, kk);
7868 if (arith ==
"real:16")
return run_ln<line::Real<16> >(file, output, kk);
7869 if (arith ==
"real" || arith ==
"real:32")
return run_ln<line::Real<32> >(file, output, kk);
7870 if (arith ==
"real:64")
return run_ln<line::Real<64> >(file, output, kk);
7871 if (arith ==
"real:128")
return run_ln<line::Real<128> >(file, output, kk);
7872 if (arith ==
"real:256")
return run_ln<line::Real<256> >(file, output, kk);
7873 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7921 const std::size_t M =
sn.nstations, K =
sn.nclasses;
7930 for (std::size_t j = 0; j < M; ++j)
7931 if (
sn.stations[j].name == a.
station_names[i]) { st_of[i] = j + 1;
break; }
7932 std::vector<std::size_t> cl_of(a.
class_names.size(), 0);
7933 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7934 for (std::size_t j = 0; j < K; ++j)
7935 if (
sn.classes[j].name == a.
class_names[c]) { cl_of[c] = j + 1;
break; }
7936 for (
int m = 0; m < 6; ++m) {
7939 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7940 if (st_of[i] && cl_of[c])
7941 (*dst[m])(st_of[i] - 1, cl_of[c] - 1) = ldes_at(*src[m], i, c);
7942 for (std::size_t f = 0; f < F; ++f)
7943 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7944 if (cl_of[c]) (*dst[m])(M + f, cl_of[c] - 1) = ldes_at(*fcr[m], f, c);
7947 for (std::size_t i = 0; i < M; ++i)
7948 for (std::size_t c = 0; c < K; ++c) RNs(i, c) = r.
RN(i, c);
7950 for (std::size_t i = 0; i < M; ++i)
7951 for (std::size_t c = 0; c < K; ++c) r.
WN(i, c) = WNs(i, c);
7952 for (std::size_t c = 0; c < K; ++c) {
7953 r.
CN.push_back(ldes_at(a.
CN, 0, c));
7954 r.
XN.push_back(ldes_at(a.
XN, 0, c));
7986 const std::string& s, std::string& banner,
7987 std::string* suffix =
nullptr,
7988 const std::string* file =
nullptr) {
7990 if (s ==
"jmt" || s ==
"ldes") {
7994 if constexpr (std::is_same_v<T, double>) {
7997 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
7998 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
7999 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
8005 banner =
"SolverJMT";
8010 std::snprintf(buf,
sizeof(buf),
" samples=%g seed=%ld", o.
samples, o.
seed);
8016 ldes_run(file ? *file : std::string(), o, std::vector<std::string>());
8017 r = avg_result_from_ldes(
sn, a);
8018 banner =
"SolverLDES";
8021 std::snprintf(buf,
sizeof(buf),
" engine=%s samples=%zu seed=%ld",
8029 }
else if (s ==
"ssa" || s ==
"fluid") {
8036 if constexpr (std::is_same_v<T, double>) {
8039 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8040 if (k.samples)
opt.samples = k.samples;
8041 if (k.seed)
opt.seed = k.seed;
8046 std::vector<line::ssa::SsaCacheRatio>
cache;
8059 banner =
"SolverSSA";
8066 std::snprintf(buf,
sizeof(buf),
" samples=%zu seed=%lu time=%.6g", a.
samples,
8084 banner =
"SolverFluid";
8108 const bool has_ref = !refreshed.
nodes.empty();
8109 r = avg_result_from_sim<T>(has_ref ? refreshed :
sn, a.
QN, a.
UN, a.
RN, a.
TN, a.
CN,
8114 banner =
"SolverFluid";
8117 std::snprintf(buf,
sizeof(buf),
" iters=%zu", a.
iters);
8124 }
else if (s ==
"nc") {
8126 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8127 if (k.tol >= 0.0)
opt.tol = k.tol;
8128 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
8129 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8130 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
8139 if constexpr (std::is_same_v<T, double>) {
8149 banner =
"SolverNC";
8150 }
else if (s ==
"mam") {
8152 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8153 if (k.tol >= 0.0)
opt.tol = k.tol;
8154 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8156 banner =
"SolverMAM";
8157 }
else if (s ==
"ag") {
8159 apply_ag_knobs(k,
opt);
8161 banner =
"SolverAG";
8162 }
else if (s ==
"ba") {
8164 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8166 banner =
"SolverBA";
8167 }
else if (s ==
"ctmc") {
8169 if (!k.method.empty())
opt.method = k.method;
8170 if (k.cutoff >= 0.0)
opt.cutoff = k.cutoff;
8171 opt.cutoff_mat = k.cutoff_mat;
8172 opt.force = k.force;
8175 banner =
"SolverCTMC";
8178 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8179 if (k.tol >= 0.0)
opt.tol = k.tol;
8180 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
8181 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8187 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
8188 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
8191 if constexpr (std::is_same_v<T, double>) {
8203 banner =
"SolverMVA";
8214int solve_model_node(
const std::string& file,
const Knobs& k,
const std::string& s) {
8217 std::string banner, suffix;
8220 const std::size_t I =
sn.nodes.size(), R =
sn.nclasses;
8221 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8222 const line::Matrix<T>&QNn = nm.QN, &UNn = nm.UN, &RNn = nm.RN, &WNn = nm.WN, &ANn = nm.AN,
8238 const std::size_t F =
8239 r.
QN.rows() >
sn.nstations ? r.
QN.rows() -
sn.nstations :
static_cast<std::size_t
>(0);
8240 const double region_nan = std::numeric_limits<double>::quiet_NaN();
8241 auto region_name = [&](std::size_t f) {
8242 return (f <
sn.regions.size() && !
sn.regions[f].name.empty())
8243 ?
sn.regions[f].name
8244 :
"FCR" + std::to_string(f + 1);
8248 auto region_empty = [&](std::size_t f, std::size_t c) {
8249 return !(d(r.
QN(
sn.nstations + f, c)) > 0.0 || d(r.
RN(
sn.nstations + f, c)) > 0.0 ||
8250 d(r.
TN(
sn.nstations + f, c)) > 0.0);
8252 if (g_json_output) {
8258 p[
"type"] =
"AvgNodeTable";
8260 for (
const char* key : {
"Node",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
8262 p[key] = line::reg::Json::array();
8263 for (std::size_t i = 0; i < I; ++i)
8264 for (std::size_t c = 0; c < R; ++c) {
8265 if (hidden[c])
continue;
8268 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
8269 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
8271 p[
"Node"].push_back(
sn.nodes[i].name);
8272 p[
"JobClass"].push_back(
sn.classes[c].name);
8273 p[
"QLen"].push_back(d(QNn(i, c)));
8274 p[
"Util"].push_back(d(UNn(i, c)));
8275 p[
"RespT"].push_back(d(RNn(i, c)));
8276 p[
"ResidT"].push_back(d(WNn(i, c)));
8277 p[
"ArvR"].push_back(d(ANn(i, c)));
8278 p[
"Tput"].push_back(d(TNn(i, c)));
8280 for (std::size_t f = 0; f < F; ++f)
8281 for (std::size_t c = 0; c < R; ++c) {
8282 if (region_empty(f, c))
continue;
8283 p[
"Node"].push_back(region_name(f));
8284 p[
"JobClass"].push_back(
sn.classes[c].name);
8285 p[
"QLen"].push_back(d(r.
QN(
sn.nstations + f, c)));
8286 p[
"Util"].push_back(region_nan);
8287 p[
"RespT"].push_back(d(r.
RN(
sn.nstations + f, c)));
8288 p[
"ResidT"].push_back(d(r.
WN(
sn.nstations + f, c)));
8289 p[
"ArvR"].push_back(region_nan);
8290 p[
"Tput"].push_back(d(r.
TN(
sn.nstations + f, c)));
8297 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Node",
"JobClass",
"QLen",
"Util",
8298 "RespT",
"ResidT",
"ArvR",
"Tput");
8299 for (std::size_t i = 0; i < I; ++i)
8300 for (std::size_t c = 0; c < R; ++c) {
8301 if (hidden[c])
continue;
8302 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
8303 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
8305 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8306 sn.nodes[i].name.c_str(),
sn.classes[c].name.c_str(), d(QNn(i, c)),
8307 d(UNn(i, c)), d(RNn(i, c)), d(WNn(i, c)), d(ANn(i, c)), d(TNn(i, c)));
8309 for (std::size_t f = 0; f < F; ++f)
8310 for (std::size_t c = 0; c < R; ++c) {
8311 if (region_empty(f, c))
continue;
8312 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8313 region_name(f).c_str(),
sn.classes[c].name.c_str(),
8314 d(r.
QN(
sn.nstations + f, c)), region_nan, d(r.
RN(
sn.nstations + f, c)),
8315 d(r.
WN(
sn.nstations + f, c)), region_nan, d(r.
TN(
sn.nstations + f, c)));
8340int solve_model_cache(
const std::string& file,
const Knobs& k,
const std::string& s) {
8343 std::string banner, suffix;
8345 if (r.
cache.empty())
8347 "-a cache reports the per-Cache hit and miss table and this model has no Cache node, "
8348 "or the solver that ran analyzes none; SolverNC's cache branches are what fill it");
8353 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8354 std::size_t srcnode = 0;
8355 for (std::size_t i = 0; i <
sn.nodes.size(); ++i)
8359 std::string node, cls;
8360 double list, listcap, items, hitp, dhitp, missp, hitr, dhitr, missr, arvr, residt, cost;
8362 std::vector<Row> rows;
8363 const double dnan = std::numeric_limits<double>::quiet_NaN();
8365 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
8367 const typename std::map<std::size_t, line::qn::CacheParam<T> >::const_iterator it =
8368 sn.nodeparam.find(m.
node);
8369 if (it ==
sn.nodeparam.end())
continue;
8370 const std::vector<std::size_t>& hitclass = it->second.hitclass;
8371 const std::size_t h = m.
itemcap.size();
8372 double totcap = 0.0;
8373 for (std::size_t l = 0; l < h; ++l) totcap += m.
itemcap[l];
8374 double totcost = dnan;
8377 for (std::size_t l = 0; l < m.
listcost.size(); ++l)
8381 for (std::size_t cl = 0; cl <
sn.nclasses; ++cl) {
8382 if (cl >= hitclass.size() || hitclass[cl] == 0)
continue;
8385 if (std::isnan(ph) && std::isnan(pm) && std::isnan(pd))
continue;
8386 if (std::isnan(ph)) ph = 0.0;
8387 if (std::isnan(pm)) pm = 0.0;
8388 if (std::isnan(pd)) pd = 0.0;
8391 const double lat = cache_at(m.
latency, cl);
8394 t.node =
sn.nodes[m.
node - 1].name;
8395 t.cls =
sn.classes[cl].name;
8398 t.items =
static_cast<double>(m.
nitems);
8403 t.dhitr = arvr * pd;
8404 t.missr = arvr * pm;
8405 t.arvr = arvr * (pm + pd);
8413 for (std::size_t l = 0; l < m.
hitproblist.cols(); ++l)
8417 for (std::size_t l = 0; l < h; ++l) {
8421 if (std::isnan(phl)) phl = 0.0;
8425 u.list =
static_cast<double>(l + 1);
8431 u.hitr = arvr * phl;
8444 if (g_json_output) {
8446 p[
"type"] =
"AvgCacheTable";
8448 for (
const char* key : {
"Node",
"JobClass",
"List",
"ListCap",
"Items",
"HitProb",
8449 "DelayedHitProb",
"MissProb",
"HitRate",
"DelayedHitRate",
8450 "MissRate",
"ArvR",
"ResidT",
"ListCost"})
8451 p[key] = line::reg::Json::array();
8452 for (std::size_t i = 0; i < rows.size(); ++i) {
8453 p[
"Node"].push_back(rows[i].node);
8454 p[
"JobClass"].push_back(rows[i].cls);
8455 p[
"List"].push_back(rows[i].list);
8456 p[
"ListCap"].push_back(rows[i].listcap);
8457 p[
"Items"].push_back(rows[i].items);
8458 p[
"HitProb"].push_back(rows[i].hitp);
8459 p[
"DelayedHitProb"].push_back(rows[i].dhitp);
8460 p[
"MissProb"].push_back(rows[i].missp);
8461 p[
"HitRate"].push_back(rows[i].hitr);
8462 p[
"DelayedHitRate"].push_back(rows[i].dhitr);
8463 p[
"MissRate"].push_back(rows[i].missr);
8464 p[
"ArvR"].push_back(rows[i].arvr);
8465 p[
"ResidT"].push_back(rows[i].residt);
8466 p[
"ListCost"].push_back(rows[i].cost);
8473 std::printf(
"%-14s %-12s %5s %8s %6s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
"Node",
8474 "JobClass",
"List",
"ListCap",
"Items",
"HitProb",
"DHitProb",
"MissProb",
8475 "HitRate",
"DHitRate",
"MissRate",
"ArvR",
"ResidT",
"ListCost");
8476 for (std::size_t i = 0; i < rows.size(); ++i)
8478 "%-14s %-12s %5g %8g %6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g "
8480 rows[i].node.c_str(), rows[i].cls.c_str(), rows[i].list, rows[i].listcap,
8481 rows[i].items, rows[i].hitp, rows[i].dhitp, rows[i].missp, rows[i].hitr,
8482 rows[i].dhitr, rows[i].missr, rows[i].arvr, rows[i].residt, rows[i].cost);
8500int solve_model_item(
const std::string& file,
const Knobs& k,
const std::string& s) {
8503 std::string banner, suffix;
8505 if (r.
cache.empty())
8507 "-a item reports the per-item cache occupancy and this model has no Cache node, or "
8508 "the solver that ran analyzes none");
8510 const double dnan = std::numeric_limits<double>::quiet_NaN();
8513 double item, list, listcap, size, prob, cost, dhq, dhqf;
8515 std::vector<Row> rows;
8516 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
8518 const std::size_t h = m.
itemcap.size();
8524 const std::size_t nit =
8526 for (std::size_t i = 0; i < nit; ++i)
8527 for (std::size_t l = 0; l < h; ++l) {
8529 t.node =
sn.nodes[m.
node - 1].name;
8530 t.item =
static_cast<double>(i + 1);
8531 t.list =
static_cast<double>(l + 1);
8536 t.prob = (l + 1) < m.
itemprob.cols()
8539 t.cost = t.size * t.prob;
8551 "-a item needs a per-item occupancy law and this solve produced none; the NC/MVA "
8552 "cache recursions (isolated and integrated alike) and the delayed-hit retrieval "
8553 "algorithms compute the embedded one, SolverCTMC the time-weighted one, and the "
8556 if (g_json_output) {
8558 p[
"type"] =
"AvgItemTable";
8560 for (
const char* key : {
"Node",
"Item",
"List",
"ListCap",
"Size",
"Prob",
"Cost",
8561 "DelayedHitQLen",
"DelayedHitQLenFull"})
8562 p[key] = line::reg::Json::array();
8563 for (std::size_t i = 0; i < rows.size(); ++i) {
8564 p[
"Node"].push_back(rows[i].node);
8565 p[
"Item"].push_back(rows[i].item);
8566 p[
"List"].push_back(rows[i].list);
8567 p[
"ListCap"].push_back(rows[i].listcap);
8568 p[
"Size"].push_back(rows[i].size);
8569 p[
"Prob"].push_back(rows[i].prob);
8570 p[
"Cost"].push_back(rows[i].cost);
8571 p[
"DelayedHitQLen"].push_back(rows[i].dhq);
8572 p[
"DelayedHitQLenFull"].push_back(rows[i].dhqf);
8579 std::printf(
"%-14s %6s %6s %8s %10s %12s %12s %14s %18s\n",
"Node",
"Item",
"List",
"ListCap",
8580 "Size",
"Prob",
"Cost",
"DelayedHitQLen",
"DelayedHitQLenFull");
8581 for (std::size_t i = 0; i < rows.size(); ++i)
8582 std::printf(
"%-14s %6g %6g %8g %10g %12.8g %12.8g %14.8g %18.8g\n", rows[i].node.c_str(),
8583 rows[i].item, rows[i].list, rows[i].listcap, rows[i].size, rows[i].prob,
8584 rows[i].cost, rows[i].dhq, rows[i].dhqf);
8597int solve_model_sys(
const std::string& file,
const Knobs& k,
const std::string& s) {
8600 std::string banner, suffix;
8607 if (g_json_output) {
8613 p[
"type"] =
"AvgSysTable";
8615 for (
const char* key : {
"Chain",
"JobClasses",
"SysRespT",
"SysTput"})
8616 p[key] = line::reg::Json::array();
8617 for (std::size_t c = 0; c <
sn.nchains; ++c) {
8618 p[
"Chain"].push_back(cn[c]);
8619 p[
"JobClasses"].push_back(cc[c]);
8620 p[
"SysRespT"].push_back(d(sys.
CN[c]));
8621 p[
"SysTput"].push_back(d(sys.
XN[c]));
8626 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner.c_str(),
8628 std::printf(
"%-10s %-24s %14s %14s\n",
"Chain",
"JobClasses",
"SysRespT",
"SysTput");
8629 for (std::size_t c = 0; c <
sn.nchains; ++c)
8630 std::printf(
"%-10s %-24s %14.6g %14.6g\n", cn[c].c_str(), cc[c].c_str(), d(sys.
CN[c]),
8637void print_chain_table(
const char* key,
const char* type,
const char* rowlabel,
8638 const std::vector<std::string>& rows,
8639 const std::vector<std::string>& chains,
8640 const std::vector<std::string>& classes,
8642 const char* banner,
const char* arith,
const char* suffix =
"") {
8644 if (g_json_output) {
8650 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
8651 chains.size(), suffix);
8655 for (
const char* c : {rowlabel,
"Chain",
"JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
8657 p[c] = line::reg::Json::array();
8661 for (std::size_t i = 0; i < rows.size(); ++i)
8662 for (std::size_t c = 0; c < chains.size(); ++c) {
8663 p[rowlabel].push_back(rows[i]);
8664 p[
"Chain"].push_back(chains[c]);
8665 p[
"JobClasses"].push_back(classes[c]);
8666 p[
"QLen"].push_back(d(t.
QN(i, c)));
8667 p[
"Util"].push_back(d(t.
UN(i, c)));
8668 p[
"RespT"].push_back(d(t.
RN(i, c)));
8669 p[
"ResidT"].push_back(d(t.
WN(i, c)));
8670 p[
"ArvR"].push_back(d(t.
AN(i, c)));
8671 p[
"Tput"].push_back(d(t.
TN(i, c)));
8673 emit_analysis<T>(key, p, method);
8676 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
8677 chains.size(), suffix);
8678 std::printf(
"%-16s %-10s %-20s %12s %12s %12s %12s %12s %12s\n", rowlabel,
"Chain",
8679 "JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
8680 for (std::size_t i = 0; i < rows.size(); ++i)
8681 for (std::size_t c = 0; c < chains.size(); ++c)
8682 std::printf(
"%-16s %-10s %-20s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8683 rows[i].c_str(), chains[c].c_str(), classes[c].c_str(), d(t.
QN(i, c)),
8684 d(t.
UN(i, c)), d(t.
RN(i, c)), d(t.
WN(i, c)), d(t.
AN(i, c)),
8698int solve_model_chain(
const std::string& file,
const Knobs& k,
const std::string& s) {
8701 std::string banner, suffix;
8704 std::vector<std::string> rows;
8705 for (std::size_t i = 0; i <
sn.nstations; ++i) rows.push_back(
sn.stations[i].name);
8706 print_chain_table<T>(
"chain",
"AvgChainTable",
"Station", rows,
8715int solve_model_nodechain(
const std::string& file,
const Knobs& k,
const std::string& s) {
8718 std::string banner, suffix;
8720 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8723 std::vector<std::string> rows;
8724 for (std::size_t i = 0; i <
sn.nodes.size(); ++i) rows.push_back(
sn.nodes[i].name);
8725 print_chain_table<T>(
"nodechain",
"AvgNodeChainTable",
"Node", rows,
8733inline bool is_avg_view(
const std::string& analysis) {
8734 return analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
8735 analysis ==
"nodechain";
8748int solve_avg_view(
const std::string& file,
const Knobs& k,
const std::string& s,
8749 const std::string& analysis) {
8750 if (analysis ==
"node")
return solve_model_node<T>(file, k, s);
8751 if (analysis ==
"sys")
return solve_model_sys<T>(file, k, s);
8752 if (analysis ==
"chain")
return solve_model_chain<T>(file, k, s);
8753 return solve_model_nodechain<T>(file, k, s);
8756int solve_model_dispatch(
const std::string& arith,
const std::string& solver,
8757 const std::string& analysis,
const std::string& file,
const Knobs& k) {
8758 std::string s = solver.empty() ?
"auto" : solver;
8759 if (s !=
"mva" && s !=
"auto" && s !=
"fluid" && s !=
"fld" && s !=
"nc" && s !=
"mam" &&
8760 s !=
"ag" && s !=
"ba" && s !=
"ssa" && s !=
"ctmc" && s !=
"uq" && s !=
"env" &&
8761 s !=
"lqns" && s !=
"ldes" && s !=
"jmt")
8763 "the model-solving path ports -s mva, nc, ctmc, mam, ag, ba, ssa, fluid, ldes, jmt, "
8764 "uq, env and lqns (got '" + s +
"'); other solvers remain API-only");
8771 if ((k.verbose && s !=
"ldes") || k.remote || !k.remote_url.empty() ||
8772 (k.keep && s !=
"lqns" && s !=
"jmt"))
8774 "--keep, --verbose, --remote and --remote-url describe the child process of an "
8775 "external solver; on this path -s jmt and -s lqns run one and take --keep, -s ldes "
8776 "runs one and takes --verbose (which echoes the resolved engine command line), and "
8777 "no path takes --remote or --remote-url");
8778 if (k.timeout_seconds && s !=
"lqns")
8780 "--timeout is the deadline of an external solver's child process; on this path only "
8781 "-s lqns runs one");
8787 if (!k.fork_join.empty() && s !=
"mva" && s !=
"nc")
8789 "--fork-join selects the fork-join transform of the shared mean-value fixed point "
8790 "and is read by -s mva and -s nc; -s " + s +
8791 " either simulates or enumerates the fork and applies no transform");
8796 if (k.warmupfrac >= 0.0 && s !=
"ssa")
8798 "--warmupfrac discards a leading fraction of a SIMULATED path before the means are "
8799 "taken and is read by -s ssa; -s " + s +
8800 " has no path to discard (the LDES engine takes --ldes-warmupfrac)");
8801 if (k.pstar > 0.0 && s !=
"fluid" && s !=
"fld")
8803 "--pstar is the exponent of the fluid p-norm smoothing of the drift and is read by "
8804 "-s fluid; -s " + s +
" integrates no drift");
8805 if ((!k.busy_orders.empty() || !k.busy_subnet.empty()) && analysis !=
"busyperiod")
8807 "--busyperiod and --busyperiod-subnet name the orders and the subnetwork of "
8808 "-a busyperiod; got -a " + analysis);
8822 if (analysis !=
"avg" && analysis !=
"posterior" && analysis !=
"interval")
8824 "SolverUQ ports -a avg (the prior-weighted expectation), -a posterior (the "
8825 "per-design-point table) and -a interval (the support-only range); got '" +
8827 if (k.uq_solver.empty())
8829 "-s uq needs --uq-solver: UQ computes nothing itself, it expands the Prior and "
8830 "runs another solver at each design point (the C++ spelling of UQ(model, "
8831 "@SolverMVA)). Naming one here by default would attribute the numbers to an "
8832 "engine the caller never chose");
8833 if (k.t1 >= 0.0 || k.node || !k.notation.empty())
8835 "--tspan, --node and --notation name a transient horizon, a stateful node and an "
8836 "ODE document; SolverUQ reports steady-state means over a design of models and "
8837 "has none of the three");
8838 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty())
8840 "--no-interlocking, --interlock-*, --repeat and --layer-solver are options of the layered solver "
8841 "and apply to -i lqnx; a Network model has no layers to interlock");
8842 if (arith ==
"double")
return solve_model_uq<double>(file, k, analysis);
8843 if (arith ==
"exact")
return solve_model_uq<line::Rational>(file, k, analysis);
8844 if (arith ==
"real:16")
return solve_model_uq<line::Real<16> >(file, k, analysis);
8845 if (arith ==
"real" || arith ==
"real:32")
8846 return solve_model_uq<line::Real<32> >(file, k, analysis);
8847 if (arith ==
"real:64")
return solve_model_uq<line::Real<64> >(file, k, analysis);
8848 if (arith ==
"real:128")
return solve_model_uq<line::Real<128> >(file, k, analysis);
8849 if (arith ==
"real:256")
return solve_model_uq<line::Real<256> >(file, k, analysis);
8850 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8859 if (analysis !=
"avg")
8861 "SolverENV reports -a avg, the environment-blended means; getEnsembleAvg is its "
8862 "only metric entry in the reference too (got '" + analysis +
"')");
8863 if (k.samples || k.seed)
8865 "--samples and --seed describe a simulation; SolverENV iterates a fixed point over "
8866 "transient stage solves and draws nothing");
8871 if (k.has_cutoff() && k.stage_solver !=
"ctmc")
8873 "--cutoff bounds the open population of an enumerated state space and applies to "
8874 "-s env only beside --stage-solver ctmc; the fluid stages of this ensemble "
8875 "enumerate no states");
8876 if (k.node || !k.notation.empty())
8878 "--node and --notation name a stateful node and an ODE document of ONE network; "
8879 "an Environment holds a network per stage and "
8880 "SolverENV reports the blend over them");
8881 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty())
8883 "--no-interlocking, --interlock-*, --repeat and --layer-solver are options of the layered solver "
8884 "and apply to -i lqnx; an Environment has stages, not layers");
8887 "--tspan on the ENV path states the transient HORIZON each stage solve integrates "
8888 "to, and every stage starts from its entry state at 0; a nonzero t0 would name a "
8889 "start the coupling has no state for");
8897 const std::string coupling =
8898 (k.method.empty() || k.method ==
"default" || k.method ==
"mean"
8899 || k.method ==
"blend" || k.method ==
"blending")
8902 if (k.tran_points && coupling ==
"statevec")
8904 "--tran-points is the mean-field coupling's quadrature grid; the state-vector "
8905 "coupling carries the whole joint law across a switch and sums over no such grid, "
8906 "so the value would be accepted and never used");
8913 if (coupling ==
"statedep")
8915 "--method statedep makes each environment transition depend on the state its "
8916 "stage is left in, through a rate function per arc that no model.json can carry "
8917 "(the reference cannot serialize resetEnvRatesFun either); it is reachable from "
8918 "the C++ API, through Environment::set_env_rate_reset");
8919 if ((k.tran_points || k.t1 >= 0.0) && (coupling ==
"avg" || coupling ==
"dec"))
8921 "--tran-points and --tspan state the grid and the horizon of a TRANSIENT stage "
8922 "solve; the closed-form limits --method avg and --method dec solve in steady "
8923 "state and carry nothing across a switch, so both would be accepted and never "
8925 if (arith !=
"double" && coupling !=
"statevec")
8927 "SolverENV solves a stage with the fluid analyzer on every method but the "
8928 "state-vector one -- the mean-field coupling transiently, the avg and dec limits "
8929 "in steady state -- and that analyzer is LSODA's and therefore double; --arith " +
8931 " reaches ENV only through the state-vector coupling (--method statevec)");
8932 if (arith ==
"double")
return solve_model_env<double>(file, k);
8933 if (arith ==
"exact")
return solve_model_env<line::Rational>(file, k);
8934 if (arith ==
"real:16")
return solve_model_env<line::Real<16> >(file, k);
8935 if (arith ==
"real" || arith ==
"real:32")
8936 return solve_model_env<line::Real<32> >(file, k);
8937 if (arith ==
"real:64")
return solve_model_env<line::Real<64> >(file, k);
8938 if (arith ==
"real:128")
return solve_model_env<line::Real<128> >(file, k);
8939 if (arith ==
"real:256")
return solve_model_env<line::Real<256> >(file, k);
8940 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8942 if (!k.uq_solver.empty())
8944 "--uq-solver names the engine SolverUQ runs at each design point and applies to -s uq; "
8945 "'" + s +
"' solves one model, not a design of them");
8948 "--tran-points is the resolution of the transient grid SolverENV sums its stage exit "
8949 "metrics over and applies to -s env; '" + s +
"' has no such quadrature");
8955 const bool ldes_knob = !k.ldes_tranfilter.empty() || k.ldes_warmupfrac >= 0.0 ||
8956 !k.ldes_cimethod.empty() || k.ldes_cnvgon || k.ldes_slotted ||
8957 k.ldes_replications > 0 || k.ldes_numthreads > 0 ||
8958 k.ldes_maxtime > 0.0 || !k.ldes_initsol.empty() ||
8959 !k.ldes_rest_url.empty();
8960 if (ldes_knob && s !=
"ldes" && s !=
"auto")
8962 "the --ldes-* flags are the discrete-event engine's own settings (warmup filter, "
8963 "confidence-interval estimator, slot lattice, replications, warm-start placement) and "
8964 "apply to -s ldes; '" + s +
"' has none of them");
8965 if (k.jmt_replications > 0 && s !=
"jmt")
8967 "--jmt-replications is SolverJMT's transient ensemble size (-s jmt -a tran / -a "
8968 "tranprob); '" + s +
"' has none (-s ldes takes --ldes-replications)");
8970 if (arith !=
"double")
8972 "SolverJMT is a client of the Java Modelling Tools engine, which simulates in "
8973 "double and reports in double; --arith " + arith +
8974 " would label a double answer with an arithmetic that never touched it");
8982 if (analysis !=
"avg" && analysis !=
"cdf" && analysis !=
"trancdf" &&
8983 analysis !=
"trancdfpasst" && analysis !=
"prob" && analysis !=
"tran" &&
8984 analysis !=
"tranprob" && analysis !=
"sample" && !is_avg_view(analysis))
8986 "-s jmt reports -a avg (the JSIM or JMVA mean table), its four views -a node, "
8987 "-a sys, -a chain and -a nodechain, -a cdf (the empirical response-time law read "
8988 "back from the JMT logs, preloaded at the rounded steady-state queue lengths), "
8989 "-a tran-cdf-respt and -a tran-cdf-passt (the same logged run from the default "
8990 "initial state, so the samples cover the transient), -a prob (the time each "
8991 "declared state is held for along the logged trajectory), -a tran (the transient "
8992 "means over --jmt-replications independent replications), -a tranprob (the same "
8993 "replications' aggregate state law at one station) and -a sample (one logged "
8994 "trajectory); the DETAILED laws -a states and getTranProb have no counterpart, "
8995 "since a JMT log records per-class job counts and nothing about the buffer order "
8996 "or the service phase");
9002 if (!k.method.empty() &&
9003 std::find(valid.begin(), valid.end(), k.method) == valid.end())
9005 "SolverJMT methods are default, jsim and the jmva family (jmva, jmva.amva, "
9006 "jmva.mva, jmva.recal, jmva.comom, jmva.chow, jmva.bs, jmva.aql, jmva.lin, "
9007 "jmva.dmlin); got '" + k.method +
"'");
9010 const bool jmt_replicated = analysis ==
"tran" || analysis ==
"tranprob";
9011 if (k.iter_max >= 0)
9013 "--iter_max is not a SolverJMT option: the number of independent replications -a "
9014 "tran and -a tranprob average over is --jmt-replications (options.config."
9015 "replications, default 10)");
9016 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || !k.multiserver.empty())
9018 "--tol, --iter_tol and --multiserver configure a fixed-point iteration; JSIM "
9019 "simulates a sample path and JMVA takes its tolerance from the exported document. "
9020 "The simulation's stopping rule is --samples");
9021 if (k.jmt_replications > 0 && !jmt_replicated)
9023 "--jmt-replications sizes the transient ensemble of -a tran and -a tranprob; "
9024 "steady-state JSIM (-a " + analysis +
") is one run and reads no replication count");
9027 "--cutoff truncates an enumerated state space; JMT enumerates none");
9034 const bool jmt_node_arm =
9035 analysis ==
"prob" || analysis ==
"tranprob" || analysis ==
"sample";
9036 if ((k.node && !jmt_node_arm) || k.jobclass || !k.marg_states.empty())
9038 "--node, --class and --marg-states select the marginal law of one (node, class); "
9039 "the JMT arms report tables over every station and class, and --node applies to "
9040 "-a prob (which station --state overrides), -a tranprob (whose state law) and "
9041 "-a sample (whose trajectory)");
9042 if (!k.state.empty() && analysis !=
"prob")
9044 "--state names the state a probability is asked about and applies to -a prob");
9045 if (!k.state.empty() && !k.node)
9047 "--state is the per-class job count of ONE station and needs --node to say "
9048 "which; a bare count vector cannot be matched against a whole network");
9049 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
9051 "--notation, --symbolic and --equilibria describe an exported ODE document; JMT "
9052 "integrates no ODE");
9053 if (!k.cdf_algorithm.empty())
9055 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
9056 "JMT response-time law is the ecdf of the passages its loggers recorded and is "
9057 "not computed from a transform");
9058 if (is_avg_view(analysis))
return solve_avg_view<double>(file, k,
"jmt", analysis);
9059 return solve_model_jmt(file, k, analysis);
9062 if (arith !=
"double")
9064 "SolverLDES is a client of the SSJ engine, which simulates in double and reports "
9065 "in double; --arith " + arith +
9066 " would label a double answer with an arithmetic that never touched it");
9074 if (!k.method.empty() && k.method !=
"default" && k.method !=
"para" && k.method !=
"parallel")
9076 "SolverLDES has the methods 'default' and 'parallel' (alias 'para'; listValidMethods returns "
9077 "exactly those in every codebase); got '" + k.method +
"'");
9079 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0 || !k.multiserver.empty())
9081 "--tol, --iter_tol, --iter_max and --multiserver are the knobs of a fixed-point "
9082 "iteration; LDES simulates a sample path and iterates nothing. Its stopping rule "
9083 "is --samples, or --ldes-cnvgon with --ldes-cnvgtol");
9086 "--cutoff truncates an enumerated state space; a simulator visits the states the "
9087 "sample path reaches and enumerates none");
9088 if (k.jobclass || !k.marg_states.empty() || (k.node && analysis !=
"prob"))
9090 "--class and --marg-states select a marginal law of one (node, class), and --node "
9091 "applies to -a prob beside --state; the other LDES arms report tables over every "
9092 "station and class");
9093 if (!k.state.empty() && (analysis !=
"prob" || !k.node))
9095 "--state is getProbAggr(node, state)'s per-class job count of ONE station: it "
9096 "applies to -s ldes -a prob and needs --node to say which");
9097 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
9099 "--notation, --symbolic and --equilibria describe an exported ODE document; LDES "
9100 "integrates no ODE");
9101 if (!k.cdf_algorithm.empty())
9103 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
9104 "LDES response-time law is the ecdf of the samples the engine recorded and is not "
9105 "computed from a transform");
9106 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty())
9108 "--no-interlocking, --interlock-*, --repeat and --layer-solver are options of the layered solver "
9109 "and apply to -i lqnx; the LDES layered path runs in the JAR's own ensemble "
9110 "backend and has no JSON interface to reach from here");
9111 if (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step >= 0.0)
9113 "--sens-method, --sens-scheme and --sens-step configure the layered sensitivity "
9114 "table; LDES reports no sensitivity");
9115 if (analysis ==
"tran" && !(k.t1 >= 0.0))
9117 "-s ldes -a tran needs --tspan <t1> or --tspan <t0>:<t1>: a trajectory over an unstated "
9118 "horizon is not a quantity, and the engine only records buckets once a timespan "
9119 "makes the run transient");
9120 if (k.t1 >= 0.0 && analysis !=
"tran")
9122 "--tspan names the horizon of -a tran; -a sample runs over [0, --samples], which "
9123 "is the horizon runTransientJson uses, and the other arms are steady state");
9130 if ((analysis ==
"trancdf" || analysis ==
"trancdfpasst") &&
9131 k.verbosity !=
"silent")
9132 std::fprintf(stderr,
9133 "Warning: -a %s is the ecdf of the per-job response times the run "
9134 "observed, the same curve -a cdf reports; the reference's LDES "
9135 "getTranCdfRespT reads the same samples\n",
9140 if ((kldes.method ==
"parallel" || kldes.method ==
"para") && kldes.ldes_replications <= 1)
9141 kldes.ldes_replications = 8;
9142 if (is_avg_view(analysis))
return solve_avg_view<double>(file, kldes,
"ldes", analysis);
9143 return solve_model_ldes(file, kldes, analysis);
9153 if (analysis ==
"aoi")
9155 "-a aoi is the AoI branch of the fluid 'mfq' method and no other engine reports "
9156 "it, so SolverAUTO does not choose for it: ask for it by name with -s fluid");
9157 const AutoPlan plan = choose_auto_plan_dispatch(arith, file, analysis, k.method);
9164 if (plan.order.size() == 1) {
9169 kk.method = plan.method;
9170 std::printf(
"SolverAUTO selected %s%s\n", plan.order[0].c_str(), plan.note.c_str());
9171 return solve_model_dispatch(arith, plan.order[0], analysis, file, kk);
9173 std::string first_error;
9174 for (std::size_t i = 0; i < plan.order.size(); ++i) {
9176 kk.method = (i == 0) ? plan.method : std::string();
9178 std::printf(
"SolverAUTO selected %s%s\n", plan.order[i].c_str(),
9181 std::printf(
"SolverAUTO retrying with %s\n", plan.order[i].c_str());
9183 return solve_model_dispatch(arith, plan.order[i], analysis, file, kk);
9185 if (first_error.empty()) first_error = plan.order[i] +
": " + e.what();
9186 std::printf(
"SolverAUTO: %s cannot serve this run (%s)\n", plan.order[i].c_str(),
9191 "SolverAUTO: every candidate refused this run. The chosen engine reported -- " +
9198 const bool is_sim = (s ==
"ssa");
9206 const bool is_cftp =
9207 (s ==
"nc" && (k.method ==
"cftp" || k.method ==
"cftp.approx"));
9222 const bool draws_samples =
9223 is_sim || (s ==
"ctmc" && analysis ==
"sample") || is_cftp || nc_draws;
9224 if (k.samples && !draws_samples)
9226 "-s ctmc -a sample and to the stochastic -s nc methods "
9227 "(mci, imci, ls, is, sampling, mcmc, cftp, cftp.approx); '" +
9228 s +
"' has no sample count");
9229 if (k.seed && !draws_samples)
9231 "-s ctmc -a sample and to the stochastic -s nc methods "
9232 "(mci, imci, ls, is, sampling, mcmc, cftp, cftp.approx); '" +
9233 s +
"' draws no random numbers");
9237 if ((k.mdd_tol > 0.0 || k.mdd_maxiter > 0) && !(s ==
"ctmc" && k.method ==
"mdd"))
9239 "--mdd-tol and --mdd-maxiter set the coupled level iteration of -s ctmc --method mdd; "
9240 "'" + s +
" / " + (k.method.empty() ? std::string(
"default") : k.method) +
9241 "' iterates no levels");
9250 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")) &&
9251 !(s ==
"fluid" || s ==
"fld") && !(s ==
"mam" && analysis ==
"tran"))
9253 "--tspan sets the horizon of a transient analysis and applies to -s ctmc -a tran, "
9254 "-a tranprob and -a tranreward, to -s mam -a tran and to -s fluid; '" + s +
" / " +
9255 analysis +
"' integrates no forward equation");
9263 if (k.node && !(s ==
"ctmc" && (analysis ==
"tranprob" || analysis ==
"sample")) &&
9264 !(s ==
"ctmc" && analysis ==
"prob" && !k.state.empty()) &&
9265 !(s ==
"nc" && analysis ==
"prob" && !k.state.empty()) &&
9266 !(s ==
"ssa" && analysis ==
"sample") && !(s ==
"mam" && analysis ==
"prob") &&
9267 !((s ==
"mva" || s ==
"nc") && analysis ==
"marg"))
9269 "--node selects the stateful node a state query is labelled by and applies to -s ctmc "
9270 "-a tranprob and -a sample, to -s ctmc|nc -a prob beside --state, to -s ssa -a sample, "
9271 "to -s mam -a prob and to -s mva|nc -a marg; '" + s +
" / " + analysis +
9272 "' reports the whole network");
9276 if ((k.jobclass || !k.marg_states.empty()) && !(s ==
"mva" && analysis ==
"marg"))
9278 "--class and --marg-states are the job class and the state list of getProbMarg and "
9279 "apply to -s mva -a marg; '" + s +
" / " + analysis +
9280 "' reports every class over its own range");
9284 if (!k.notation.empty() && !((s ==
"fluid" || s ==
"fld") && analysis ==
"odes"))
9286 "--notation selects the form of the exported ODE document and applies to -s fluid -a "
9287 "odes; '" + s +
" / " + analysis +
"' exports no equations");
9290 if ((!k.symbolic.empty() || k.equilibria) &&
9291 !((s ==
"fluid" || s ==
"fld") && analysis ==
"jacobian"))
9293 "--symbolic selects the computer-algebra backend and --equilibria asks it for the "
9294 "solutions of f(x) = 0; both apply to -s fluid -a jacobian, and '" + s +
" / " +
9295 analysis +
"' consults no backend");
9304 if (!k.state.empty() &&
9305 !(analysis ==
"prob" && (s ==
"ctmc" || s ==
"auto" || s ==
"jmt" || s ==
"nc")))
9307 "--state names the state `getProb(node, state)` asks about and applies to -a prob "
9308 "under -s ctmc, -s nc and -s jmt, the arms whose answer is indexed by a state; '" + s +
9309 " / " + analysis +
"' reports a mean or a law over all of them");
9310 if (k.events && analysis !=
"sample")
9312 "--events is the length of ONE sampled trajectory and applies to -a sample; use "
9313 "--samples for a solver's run length ('" + s +
" / " + analysis +
"')");
9314 if (!k.percentiles.empty() && !(s ==
"mam" && (analysis ==
"cdf" || analysis ==
"cdfpasst" ||
9315 analysis ==
"perct")))
9317 "--percentiles names the levels getPerctRespT is read at and applies to -s mam -a "
9318 "perct-respt (and to the percentiles printed beside -a cdf); '" + s +
" / " +
9319 analysis +
"' inverts no response-time law");
9320 if (!k.reward_name.empty() && analysis !=
"rewardvalue")
9322 "--reward-name selects which declared reward -a reward-value returns the value "
9323 "function of; -a reward returns every reward's steady-state expectation and needs no "
9324 "name ('" + s +
" / " + analysis +
"')");
9325 if (k.timestep > 0.0 &&
9327 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
9329 "--timestep is the fixed output grid of a transient CTMC solve, `options.timestep` of "
9330 "ctmc_transient.m, and applies to -s ctmc -a tran, -a tranprob and -a tranreward; the "
9332 "and simulated transients report the points their own integrator or engine produced "
9333 "('" + s +
" / " + analysis +
"')");
9334 if ((!k.rate_sched.empty() || k.ctmc_tv_ngrid > 0) && !(s ==
"ctmc" && analysis ==
"tran"))
9336 "--rate-sched (and --ctmc-tv-ngrid) makes the CTMC generator time-inhomogeneous, "
9337 "`options.config.rate_sched` of solver_ctmc_transient_analyzer.m, and applies to "
9338 "-s ctmc -a tran only ('" + s +
" / " + analysis +
"')");
9339 if ((!k.transient_method.empty() || k.fau_epsilon > 0.0 || k.fau_delta >= 0.0) &&
9341 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
9343 "--transient-method (and --fau-epsilon / --fau-delta) selects how the CTMC forward "
9344 "equation is advanced, `options.config.transient_method` of "
9345 "solver_ctmc_transient_analyzer.m, and applies to -s ctmc -a tran, -a tranprob and "
9346 "-a tranreward; every other analysis solves no forward equation ('" +
9347 s +
" / " + analysis +
"')");
9351 if (!k.cdf_algorithm.empty() && !(s ==
"nc" && analysis ==
"cdf"))
9353 "--cdf-algorithm selects the sojourn-time inversion of the NC response-time "
9354 "distribution and applies to -s nc -a cdf; '" + s +
" / " + analysis +
9355 "' inverts no generating function");
9360 const bool passage_arm =
9361 (s ==
"ctmc" && (analysis ==
"firstpasst" || analysis ==
"firstpasstmom"));
9362 if ((!k.passage_from.empty() || !k.passage_into.empty() || k.passage_orders > 0) &&
9365 "--passage-from, --passage-into and --passage-orders name the state sets and the "
9366 "moment order of -s ctmc -a firstpasst / firstpasstmom; '" + s +
" / " + analysis +
9367 "' times no state-set passage");
9368 if (!k.passage_method.empty() && !(s ==
"ctmc" && analysis ==
"firstpasst"))
9370 "--passage-method selects the transform inversion of -s ctmc -a firstpasst; '" + s +
9371 " / " + analysis +
"' inverts none (the moments arm solves for them directly)");
9374 if (k.method_perm !=
"exact" && !(s ==
"nc" && analysis ==
"sysmarg"))
9376 "--perm-engine selects the permanent estimator of the NC joint total-queue-length "
9377 "law and applies to -s nc -a sysmarg; '" + s +
" / " + analysis +
9378 "' evaluates no permanent");
9384 const bool nc_sens = s ==
"nc" && analysis ==
"sens";
9385 if (!nc_sens && (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step > 0.0))
9387 "--sens-method, --sens-scheme and --sens-step select the branch of a sensitivity "
9388 "table and apply to -i lqnx -a sens or to -s nc -a sens; '" + s +
" / " + analysis +
9389 "' differentiates nothing");
9390 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty() ||
9391 !k.ln_transient.empty() || !k.ln_transient_channels.empty())
9393 "--no-interlocking, --interlock-*, --repeat, --layer-solver and --ln-transient* are options "
9394 "of the layered solver and apply to -i lqnx; a Network model has no layers to "
9396 if (s ==
"ba" && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
9398 "--tol, --iter_tol and --iter_max do not apply to -s ba: a bound is a closed form, "
9399 "with nothing to converge");
9403 if (s !=
"ba" && (!k.qrf_params.empty() || !k.qrf_alpha.empty()))
9405 "--qrf-params and --qrf-alpha parameterise the QRF reduction bounds and apply to "
9406 "-s ba; '" + s +
"' solves no reduction program");
9407 if (s !=
"ba" && k.level > 0)
9409 "--level is the hierarchy level of the SolverBA bound families and applies to -s ba; "
9410 "'" + s +
"' has no bound hierarchy");
9411 if (is_sim && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
9413 "--tol, --iter_tol and --iter_max do not apply to -s ssa: a sample path is not an "
9414 "iteration; use --samples to set its length");
9415 if (s ==
"mam" && k.iter_tol >= 0.0)
9417 "--iter_tol is not a SolverMAM option (MamOptions carries tol and iter_max); "
9419 if (s ==
"ag" && k.iter_tol >= 0.0)
9421 "--iter_tol is not a SolverAG option (AgOptions carries tol and iter_max, the "
9422 "tolerance and the sweep budget of the reversed-rate fixed point); use --tol");
9429 if (k.max_states >= 0 && s !=
"ag")
9431 "--max-states truncates the queue-length dimension of a SolverAG agent and applies "
9432 "to -s ag; '" + s +
"' truncates no agent (use --cutoff for a CTMC state space)");
9438 if (k.has_cutoff() && s !=
"ctmc" && !(s ==
"mam" && analysis ==
"prob") && s !=
"env")
9440 "--cutoff bounds the open population of a CTMC state space, and the level truncation "
9441 "of -s mam -a prob; '" + s +
" / " + analysis +
"' enumerates no states");
9447 if (!k.cutoff_mat.empty() && s !=
"ctmc")
9449 "--cutoff as a per-(station,class) matrix bounds an enumerated state space per "
9450 "station and applies to -s ctmc; '" + s +
"' takes one number");
9455 if (!k.stage_solver.empty() && s !=
"env")
9457 "--stage-solver names the solver each stage of a random environment is run with and "
9458 "applies to -s env; '" + s +
"' has no stages");
9459 if (!k.stage_solver.empty() && k.stage_solver !=
"fluid" && k.stage_solver !=
"ctmc" &&
9460 k.stage_solver !=
"mam")
9462 "--stage-solver '" + k.stage_solver +
9463 "' is not available: the environment coupling needs a TRANSIENT stage solve, and only "
9464 "the fluid analyzer, the enumerated CTMC and the flattened LD-QBD provide one in this "
9469 if (!k.map_env.empty() && k.map_env !=
"default" && k.map_env !=
"off")
9471 "' is not a value: use 'default' or 'off'");
9472 if (!k.map_env_method.empty() && k.map_env_method !=
"auto" && k.map_env_method !=
"dec" &&
9473 k.map_env_method !=
"avg" && k.map_env_method !=
"meanfield")
9475 "--map-env-method '" + k.map_env_method +
9476 "' is not an environment recombination: use 'auto', 'meanfield', 'dec' or 'avg'");
9477 if ((!k.map_env.empty() || !k.map_env_method.empty() || k.map_env_maxstages) &&
9478 (s ==
"ba" || s ==
"env"))
9480 "--map-env* asks a solver to fall back on a random-environment image of a non-renewal "
9481 "process; '-s " + s +
9482 "' takes no such fallback (a bound must not be computed on an approximation, and the "
9483 "environment solver already takes a model with stages)");
9488 if (k.stage_solver ==
"mam" && k.method !=
"statevec")
9490 "--stage-solver mam applies to -s env --method statevec: the LD-QBD backend flattens "
9491 "its blocks into a generator the state-vector coupling propagates a distribution "
9492 "across, and the mean-field coupling carries marginal MEANS instead");
9497 if ((k.fj_accuracy > 0 || !k.fj_tmode.empty()) && s !=
"mam")
9499 "--fj-accuracy and --fj-tmode configure the FJ_codes fork-join approximation of "
9500 "solver_mam_fj.m and apply to -s mam; '" + s +
"' does not run it");
9505 if (!k.timescale.empty() && s !=
"mam")
9507 "--timescale selects the time scale of the MAM discrete-time path and applies to "
9508 "-s mam; '" + s +
"' does not read it (SolverNC takes --slotted)");
9527 if (analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
9528 analysis ==
"nodechain" || analysis ==
"cache" || analysis ==
"item") {
9529 const bool is_sim_engine = (s ==
"ssa" || s ==
"fluid");
9549 const bool cache_table = (analysis ==
"cache" || analysis ==
"item");
9550 const bool sim_cache_ok = (analysis ==
"cache");
9553 const bool ag_view = (s ==
"ag" && !cache_table);
9554 if ((s !=
"mva" && s !=
"auto" && s !=
"nc" && s !=
"mam" && s !=
"ba" && s !=
"ctmc" &&
9555 !ag_view && !is_sim_engine) ||
9556 (cache_table && is_sim_engine && !sim_cache_ok))
9562 ? std::string(
"-a node reports the per-node table of -s mva, nc, mam, ag, ba, "
9563 "ctmc, ssa, fluid, jmt, ldes and auto; '")
9564 :
"-a " + analysis +
9565 " is a view of the station AvgResult and is reported by -s mva, nc, "
9566 "mam, " + (analysis ==
"item" ?
"" :
"ag, ") +
"ba, ctmc" +
9567 (analysis ==
"item" ?
"" :
", ssa, fluid, jmt, ldes") +
" and auto; '") +
9568 s +
"' does not return the station AvgResult it is built from");
9574 if (is_sim_engine && arith !=
"double")
9576 "-a " + analysis +
" under -s " + s +
9577 " is read off a " + (s ==
"ssa" ?
"sample path" :
"fluid trajectory") +
9578 ", which is transcendental; rerun with --arith double (got '" + arith +
"')");
9579 const std::string eng = (s ==
"auto") ? std::string(
"mva") : s;
9580#define LINE_CLI_TABLE_LADDER(FN) \
9582 if (arith == "double") return FN<double>(file, k, eng); \
9583 if (arith == "exact") return FN<line::Rational>(file, k, eng); \
9584 if (arith == "real:16") return FN<line::Real<16> >(file, k, eng); \
9585 if (arith == "real" || arith == "real:32") return FN<line::Real<32> >(file, k, eng); \
9586 if (arith == "real:64") return FN<line::Real<64> >(file, k, eng); \
9587 if (arith == "real:128") return FN<line::Real<128> >(file, k, eng); \
9588 if (arith == "real:256") return FN<line::Real<256> >(file, k, eng); \
9596#undef LINE_CLI_TABLE_LADDER
9597 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9606 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"gen" && analysis !=
"states" &&
9607 analysis !=
"tran" && analysis !=
"tranprob" && analysis !=
"tranreward" &&
9608 analysis !=
"sample" && analysis !=
"reward" && analysis !=
"rewardvalue" &&
9609 analysis !=
"cdf" && analysis !=
"sens" && analysis !=
"firstpasst" &&
9610 analysis !=
"firstpasstmom")
9612 "the CTMC solver ports -a avg, node, sys, chain, nodechain, prob, gen, states, "
9613 "tran, tranprob, tranreward, sample, reward, reward-value, cdf, first-passt, "
9614 "first-passt-moments and sens (got '" + analysis +
"')");
9620 if (k.method ==
"mdd" && analysis !=
"avg")
9622 "the '" + k.method +
9623 "' method never builds the explicit generator, so it serves -a avg only (got '" +
9624 analysis +
"'); use --method default for the state-space analyses");
9625 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0)
9627 "--tol, --iter_tol and --iter_max do not apply to -s ctmc: the stationary vector "
9628 "is obtained by a direct solve of pi Q = 0, with nothing to converge. The mdd "
9629 "method's level iteration has --mdd-tol and --mdd-maxiter of its own");
9633 if (arith ==
"double")
return solve_model_ctmc<double>(file, k, analysis);
9634 if (arith ==
"exact")
return solve_model_ctmc<line::Rational>(file, k, analysis);
9635 if (arith ==
"real:16")
return solve_model_ctmc<line::Real<16> >(file, k, analysis);
9636 if (arith ==
"real" || arith ==
"real:32")
9637 return solve_model_ctmc<line::Real<32> >(file, k, analysis);
9638 if (arith ==
"real:64")
return solve_model_ctmc<line::Real<64> >(file, k, analysis);
9639 if (arith ==
"real:128")
return solve_model_ctmc<line::Real<128> >(file, k, analysis);
9640 if (arith ==
"real:256")
return solve_model_ctmc<line::Real<256> >(file, k, analysis);
9641 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9653 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"cdf" &&
9654 analysis !=
"cdfpasst" && analysis !=
"perct" && analysis !=
"tran" &&
9655 analysis !=
"internals")
9657 "the MAM solver ports -a avg, node, prob, cdf, cdf-passt, perct-respt, tran and "
9658 "internals (got '" + analysis +
"')");
9659 if (arith !=
"double")
9661 "the MAM solver fits phase-type representations, whose fitter requires "
9662 "transcendental arithmetic; rerun with --arith double (got '" + arith +
"')");
9663 if (analysis ==
"tran" && k.t1 < 0.0)
9665 "-s mam -a tran integrates the transient queue length over a horizon and there is "
9666 "no default for it; pass --tspan t0 t1");
9667 if (analysis ==
"prob")
return solve_model_mam_prob<double>(file, k);
9668 if (analysis ==
"cdf")
return solve_model_mam_cdf<double>(file, k,
"cdf",
"CdfRespT");
9675 if (analysis ==
"cdfpasst")
9676 return solve_model_mam_cdf<double>(file, k,
"cdfpasst",
"CdfPassT");
9677 if (analysis ==
"perct")
return solve_model_mam_perct<double>(file, k);
9678 if (analysis ==
"tran")
return solve_model_mam_tran<double>(file, k);
9679 if (analysis ==
"internals")
return solve_model_mam_internals<double>(file, k);
9680 return solve_model_mam<double>(file, k);
9688 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"sample")
9691 if (arith !=
"double")
9693 "an SSA sample path is generated from exponential clocks, which are "
9694 "transcendental; rerun with --arith double (got '" + arith +
"')");
9695 if (analysis ==
"prob")
return solve_model_ssa_prob<double>(file, k);
9696 if (analysis ==
"sample")
return solve_model_ssa_sample<double>(file, k);
9697 return solve_model_ssa<double>(file, k);
9700 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"marg" &&
9701 analysis !=
"sysmarg" && analysis !=
"cdf" && analysis !=
"sens" &&
9702 analysis !=
"normconst" && analysis !=
"busyperiod")
9704 "the NC solver ports -a avg, -a node, -a prob, -a marg, -a sysmarg, -a cdf, "
9705 "-a sens, -a normconst and -a busyperiod (got '" + analysis +
"')");
9710 if ((k.method ==
"cftp" || k.method ==
"cftp.approx") && analysis !=
"avg")
9712 "the '" + k.method +
9713 "' method draws stationary states and yields no normalizing constant, so it "
9714 "serves -a avg only (got '" + analysis +
"'); use another --method for the "
9715 "other NC analyses");
9716 if (analysis ==
"busyperiod") {
9717 if (arith ==
"double")
return solve_model_nc_busyp<double>(file, k);
9718 if (arith ==
"exact")
return solve_model_nc_busyp<line::Rational>(file, k);
9719 if (arith ==
"real:16")
return solve_model_nc_busyp<line::Real<16> >(file, k);
9720 if (arith ==
"real" || arith ==
"real:32")
9721 return solve_model_nc_busyp<line::Real<32> >(file, k);
9722 if (arith ==
"real:64")
return solve_model_nc_busyp<line::Real<64> >(file, k);
9723 if (arith ==
"real:128")
return solve_model_nc_busyp<line::Real<128> >(file, k);
9724 if (arith ==
"real:256")
return solve_model_nc_busyp<line::Real<256> >(file, k);
9725 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9727 if (analysis ==
"sysmarg") {
9728 if (arith ==
"double")
return solve_model_nc_sysmarg<double>(file, k);
9729 if (arith ==
"exact")
return solve_model_nc_sysmarg<line::Rational>(file, k);
9730 if (arith ==
"real:16")
return solve_model_nc_sysmarg<line::Real<16> >(file, k);
9731 if (arith ==
"real" || arith ==
"real:32")
9732 return solve_model_nc_sysmarg<line::Real<32> >(file, k);
9733 if (arith ==
"real:64")
return solve_model_nc_sysmarg<line::Real<64> >(file, k);
9734 if (arith ==
"real:128")
return solve_model_nc_sysmarg<line::Real<128> >(file, k);
9735 if (arith ==
"real:256")
return solve_model_nc_sysmarg<line::Real<256> >(file, k);
9736 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9738 if (analysis ==
"marg") {
9739 if (arith ==
"double")
return solve_model_nc_marg<double>(file, k);
9740 if (arith ==
"exact")
return solve_model_nc_marg<line::Rational>(file, k);
9741 if (arith ==
"real:16")
return solve_model_nc_marg<line::Real<16> >(file, k);
9742 if (arith ==
"real" || arith ==
"real:32")
9743 return solve_model_nc_marg<line::Real<32> >(file, k);
9744 if (arith ==
"real:64")
return solve_model_nc_marg<line::Real<64> >(file, k);
9745 if (arith ==
"real:128")
return solve_model_nc_marg<line::Real<128> >(file, k);
9746 if (arith ==
"real:256")
return solve_model_nc_marg<line::Real<256> >(file, k);
9747 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9751 if (analysis ==
"normconst") {
9752 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
9753 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
9754 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
9755 if (arith ==
"real" || arith ==
"real:32")
9756 return solve_model_normconst<line::Real<32> >(file, k, s);
9757 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
9758 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
9759 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
9760 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9762 if (analysis ==
"sens") {
9763 if (arith ==
"double")
return solve_model_nc_sens<double>(file, k);
9764 if (arith ==
"exact")
return solve_model_nc_sens<line::Rational>(file, k);
9765 if (arith ==
"real:16")
return solve_model_nc_sens<line::Real<16> >(file, k);
9766 if (arith ==
"real" || arith ==
"real:32")
9767 return solve_model_nc_sens<line::Real<32> >(file, k);
9768 if (arith ==
"real:64")
return solve_model_nc_sens<line::Real<64> >(file, k);
9769 if (arith ==
"real:128")
return solve_model_nc_sens<line::Real<128> >(file, k);
9770 if (arith ==
"real:256")
return solve_model_nc_sens<line::Real<256> >(file, k);
9771 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9773 if (analysis ==
"cdf") {
9774 if (arith ==
"double")
return solve_model_nc_cdf<double>(file, k);
9775 if (arith ==
"exact")
return solve_model_nc_cdf<line::Rational>(file, k);
9776 if (arith ==
"real:16")
return solve_model_nc_cdf<line::Real<16> >(file, k);
9777 if (arith ==
"real" || arith ==
"real:32")
9778 return solve_model_nc_cdf<line::Real<32> >(file, k);
9779 if (arith ==
"real:64")
return solve_model_nc_cdf<line::Real<64> >(file, k);
9780 if (arith ==
"real:128")
return solve_model_nc_cdf<line::Real<128> >(file, k);
9781 if (arith ==
"real:256")
return solve_model_nc_cdf<line::Real<256> >(file, k);
9782 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9784 if (analysis ==
"prob") {
9785 if (arith ==
"double")
return solve_model_nc_prob<double>(file, k);
9786 if (arith ==
"exact")
return solve_model_nc_prob<line::Rational>(file, k);
9787 if (arith ==
"real:16")
return solve_model_nc_prob<line::Real<16> >(file, k);
9788 if (arith ==
"real" || arith ==
"real:32")
9789 return solve_model_nc_prob<line::Real<32> >(file, k);
9790 if (arith ==
"real:64")
return solve_model_nc_prob<line::Real<64> >(file, k);
9791 if (arith ==
"real:128")
return solve_model_nc_prob<line::Real<128> >(file, k);
9792 if (arith ==
"real:256")
return solve_model_nc_prob<line::Real<256> >(file, k);
9793 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9795 if (arith ==
"double")
return solve_model_nc<double>(file, k);
9796 if (arith ==
"exact")
return solve_model_nc<line::Rational>(file, k);
9797 if (arith ==
"real:16")
return solve_model_nc<line::Real<16> >(file, k);
9798 if (arith ==
"real" || arith ==
"real:32")
return solve_model_nc<line::Real<32> >(file, k);
9799 if (arith ==
"real:64")
return solve_model_nc<line::Real<64> >(file, k);
9800 if (arith ==
"real:128")
return solve_model_nc<line::Real<128> >(file, k);
9801 if (arith ==
"real:256")
return solve_model_nc<line::Real<256> >(file, k);
9802 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9810 if (analysis !=
"avg" && analysis !=
"cdf")
9812 "the AG solver ports -a avg (with its views -a node, -a sys, -a chain and "
9813 "-a nodechain) and -a cdf, the inherited exponential fallback: RCAT converges a "
9814 "fixed point over the synchronization rates and reports mean measures, forming no "
9815 "state probability or transient (got '" + analysis +
"')");
9816 if (analysis ==
"cdf") {
9817 if (arith ==
"double")
return solve_model_ag_cdf<double>(file, k);
9818 if (arith ==
"exact")
return solve_model_ag_cdf<line::Rational>(file, k);
9819 if (arith ==
"real:16")
return solve_model_ag_cdf<line::Real<16> >(file, k);
9820 if (arith ==
"real" || arith ==
"real:32")
9821 return solve_model_ag_cdf<line::Real<32> >(file, k);
9822 if (arith ==
"real:64")
return solve_model_ag_cdf<line::Real<64> >(file, k);
9823 if (arith ==
"real:128")
return solve_model_ag_cdf<line::Real<128> >(file, k);
9824 if (arith ==
"real:256")
return solve_model_ag_cdf<line::Real<256> >(file, k);
9825 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9827 if (arith ==
"double")
return solve_model_ag<double>(file, k);
9828 if (arith ==
"exact")
return solve_model_ag<line::Rational>(file, k);
9829 if (arith ==
"real:16")
return solve_model_ag<line::Real<16> >(file, k);
9830 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ag<line::Real<32> >(file, k);
9831 if (arith ==
"real:64")
return solve_model_ag<line::Real<64> >(file, k);
9832 if (arith ==
"real:128")
return solve_model_ag<line::Real<128> >(file, k);
9833 if (arith ==
"real:256")
return solve_model_ag<line::Real<256> >(file, k);
9834 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9837 if (analysis !=
"avg" && analysis !=
"bounds" && analysis !=
"cdf")
9839 "-a cdf, the inherited exponential fallback (got '" +
9841 if (analysis ==
"cdf") {
9842 if (arith ==
"double")
return solve_model_ba_cdf<double>(file, k);
9843 if (arith ==
"exact")
return solve_model_ba_cdf<line::Rational>(file, k);
9844 if (arith ==
"real:16")
return solve_model_ba_cdf<line::Real<16> >(file, k);
9845 if (arith ==
"real" || arith ==
"real:32")
9846 return solve_model_ba_cdf<line::Real<32> >(file, k);
9847 if (arith ==
"real:64")
return solve_model_ba_cdf<line::Real<64> >(file, k);
9848 if (arith ==
"real:128")
return solve_model_ba_cdf<line::Real<128> >(file, k);
9849 if (arith ==
"real:256")
return solve_model_ba_cdf<line::Real<256> >(file, k);
9850 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9852 if (analysis ==
"bounds") {
9853 if (arith ==
"double")
return solve_model_ba_bounds<double>(file, k);
9854 if (arith ==
"exact")
return solve_model_ba_bounds<line::Rational>(file, k);
9855 if (arith ==
"real:16")
return solve_model_ba_bounds<line::Real<16> >(file, k);
9856 if (arith ==
"real" || arith ==
"real:32")
9857 return solve_model_ba_bounds<line::Real<32> >(file, k);
9858 if (arith ==
"real:64")
return solve_model_ba_bounds<line::Real<64> >(file, k);
9859 if (arith ==
"real:128")
return solve_model_ba_bounds<line::Real<128> >(file, k);
9860 if (arith ==
"real:256")
return solve_model_ba_bounds<line::Real<256> >(file, k);
9861 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9863 if (arith ==
"double")
return solve_model_ba<double>(file, k);
9864 if (arith ==
"exact")
return solve_model_ba<line::Rational>(file, k);
9865 if (arith ==
"real:16")
return solve_model_ba<line::Real<16> >(file, k);
9866 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ba<line::Real<32> >(file, k);
9867 if (arith ==
"real:64")
return solve_model_ba<line::Real<64> >(file, k);
9868 if (arith ==
"real:128")
return solve_model_ba<line::Real<128> >(file, k);
9869 if (arith ==
"real:256")
return solve_model_ba<line::Real<256> >(file, k);
9870 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9873 if (analysis !=
"avg" && analysis !=
"cdf")
9875 "SolverLQNS on a Network reports -a avg and -a cdf, the inherited exponential "
9877 "qnsolver returns one chain-level table of means and computes no state "
9878 "probability and no transient (got '" + analysis +
"')");
9882 if (arith !=
"double")
9884 "SolverLQNS reads its results back as the decimal text an external binary printed, "
9885 "which is double at best; --arith " + arith +
9886 " would report a precision the tool never produced");
9887 if (analysis ==
"cdf")
return solve_model_lqns_network_cdf(file, k);
9888 return solve_model_lqns_network<double>(file, k);
9890 if (s ==
"fluid" || s ==
"fld") {
9891 if (analysis !=
"avg" && analysis !=
"odes" && analysis !=
"var" &&
9892 analysis !=
"tranvar" && analysis !=
"jacobian" && analysis !=
"tran" &&
9893 analysis !=
"prob" && analysis !=
"cdf" && analysis !=
"aoi" &&
9894 analysis !=
"statevec")
9896 "the fluid solver ports -a avg, -a tran, -a tranvar, -a prob, -a cdf, -a aoi, "
9897 "-a odes, -a statevec, -a var and -a jacobian (got '" + analysis +
"')");
9900 if (arith !=
"double")
9902 "the fluid solver integrates its drift with LSODA, which is double precision by "
9903 "construction; rerun with --arith double (got '" + arith +
"')");
9904 if (analysis ==
"odes")
return solve_model_fluid_odes<double>(file, k);
9905 if (analysis ==
"statevec")
return solve_model_fluid_statevec<double>(file, k);
9906 if (analysis ==
"jacobian")
return solve_model_fluid_jacobian<double>(file, k);
9907 if (analysis ==
"var")
return solve_model_fluid_var<double>(file, k);
9908 if (analysis ==
"tranvar")
return solve_model_fluid_tranvar<double>(file, k);
9909 if (analysis ==
"tran")
return solve_model_fluid_tran<double>(file, k);
9910 if (analysis ==
"prob")
return solve_model_fluid_prob<double>(file, k);
9911 if (analysis ==
"cdf")
return solve_model_fluid_cdf<double>(file, k);
9912 if (analysis ==
"aoi")
return solve_model_fluid_aoi<double>(file, k);
9913 return solve_model_fluid<double>(file, k);
9915 if (analysis ==
"prob") {
9916 if (arith ==
"double")
return solve_model_prob<double>(file, k);
9917 if (arith ==
"exact")
return solve_model_prob<line::Rational>(file, k);
9918 if (arith ==
"real:16")
return solve_model_prob<line::Real<16> >(file, k);
9919 if (arith ==
"real" || arith ==
"real:32")
return solve_model_prob<line::Real<32> >(file, k);
9920 if (arith ==
"real:64")
return solve_model_prob<line::Real<64> >(file, k);
9921 if (arith ==
"real:128")
return solve_model_prob<line::Real<128> >(file, k);
9922 if (arith ==
"real:256")
return solve_model_prob<line::Real<256> >(file, k);
9923 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9925 if (analysis ==
"marg") {
9926 if (arith ==
"double")
return solve_model_marg<double>(file, k);
9927 if (arith ==
"exact")
return solve_model_marg<line::Rational>(file, k);
9928 if (arith ==
"real:16")
return solve_model_marg<line::Real<16> >(file, k);
9929 if (arith ==
"real" || arith ==
"real:32")
return solve_model_marg<line::Real<32> >(file, k);
9930 if (arith ==
"real:64")
return solve_model_marg<line::Real<64> >(file, k);
9931 if (arith ==
"real:128")
return solve_model_marg<line::Real<128> >(file, k);
9932 if (arith ==
"real:256")
return solve_model_marg<line::Real<256> >(file, k);
9933 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9935 if (analysis ==
"normconst") {
9936 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
9937 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
9938 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
9939 if (arith ==
"real" || arith ==
"real:32")
9940 return solve_model_normconst<line::Real<32> >(file, k, s);
9941 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
9942 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
9943 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
9944 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9946 if (analysis ==
"cdf") {
9949 if (arith ==
"double")
return solve_model_mva_cdf<double>(file, k);
9950 if (arith ==
"exact")
return solve_model_mva_cdf<line::Rational>(file, k);
9951 if (arith ==
"real:16")
return solve_model_mva_cdf<line::Real<16> >(file, k);
9952 if (arith ==
"real" || arith ==
"real:32")
9953 return solve_model_mva_cdf<line::Real<32> >(file, k);
9954 if (arith ==
"real:64")
return solve_model_mva_cdf<line::Real<64> >(file, k);
9955 if (arith ==
"real:128")
return solve_model_mva_cdf<line::Real<128> >(file, k);
9956 if (arith ==
"real:256")
return solve_model_mva_cdf<line::Real<256> >(file, k);
9957 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9959 if (analysis !=
"avg")
9961 "the model-solving path ports -a avg, -a node, -a prob, -a marg, -a normconst and "
9969 if (arith ==
"double")
return solve_model_mva<double>(file, k);
9970 if (arith ==
"exact")
return solve_model_mva<line::Rational>(file, k);
9971 if (arith ==
"real:16")
return solve_model_mva<line::Real<16> >(file, k);
9972 if (arith ==
"real" || arith ==
"real:32")
return solve_model_mva<line::Real<32> >(file, k);
9973 if (arith ==
"real:64")
return solve_model_mva<line::Real<64> >(file, k);
9974 if (arith ==
"real:128")
return solve_model_mva<line::Real<128> >(file, k);
9975 if (arith ==
"real:256")
return solve_model_mva<line::Real<256> >(file, k);
9977 "--arith '" + arith +
9978 "' is not a model-solve backend; use double, exact or real:<16|32|64|128|256>");
9988void print_brief_help() {
9990 "LINE solver (C++), version %s\n"
9992 "Usage: line-cli -f <model> [-s <solver>] [-a <analysis>] [-o <format>]\n"
9993 " cat model.json | line-cli\n"
9996 " -f, --file <path> model file: .json (network), .lqnx (layered),\n"
9997 " .jsimg (JMT), .pnml (Petri net); stdin if omitted\n"
9998 " -s, --solver <name> auto (default), mva, nc, ctmc, mam, fluid, ssa,\n"
9999 " ldes, jmt, ag, ba, uq; ln for layered models, env\n"
10000 " for random environments\n"
10001 " -a, --analysis <type> avg (default), node, sys, chain, tran, prob, cdf,\n"
10002 " states, sample, normconst, bounds, ... (comma list)\n"
10003 " -o, --output <fmt> readable (default) | json | jsimg (export the\n"
10004 " model as a JMT simulation document)\n"
10005 " --method <name> algorithm within the chosen solver\n"
10006 " --samples <n> simulation run length (ssa, ldes); default 10000\n"
10007 " --seed <n> random seed; default 23000\n"
10008 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
10009 " the solver console, a running progress log\n"
10010 " --find-solver [m] which solvers and methods can analyze this model,\n"
10011 " optionally only those answering measure <m>\n"
10012 " (avg, tran, cdf, prob, sample, ...); reports and exits\n"
10013 " --find-solver-all [m] the same, keeping the refused pairs and the\n"
10014 " reason each was refused\n"
10015 " -h, --help this message\n"
10016 " --help-all every option, solver by solver\n"
10017 " -V, --version version string\n"
10018 " --install environment check: which optional backends\n"
10019 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
10022 " line-cli -f model.json solve, letting auto pick the solver\n"
10023 " line-cli -f model.json -s mva -a avg mean queue lengths, MVA\n"
10024 " line-cli -f model.lqnx -s ln solve a layered model\n"
10025 " line-cli -f model.json -s ssa --samples 1e6 -o json\n"
10026 " line-cli -f model.json --find-solver what can solve this model\n"
10027 " line-cli -f model.json --find-solver cdf ... and return a passage-time law\n"
10029 "Each solver has flags of its own (tolerances, horizons, engine choices):\n"
10030 "run `line-cli --help-all` for the full reference.\n",
10036 "LINE multiprecision solver (C++), version %s\n"
10038 "Usage: line-cli [OPTIONS]\n"
10039 " cat model.json | line-cli -i json [OPTIONS]\n"
10040 " line-cli model.lqnx [OPTIONS]\n"
10042 "Options (flag-compatible with jline.cli.LineCLI):\n"
10043 " -f, --file <path> model file; stdin when omitted (json only)\n"
10044 " -i, --input <fmt> input format: json | jsim | jsimg | jsimw |\n"
10045 " lqnx | xml | pnml. Without it a .lqnx or .xml\n"
10046 " path is read as a layered model, a\n"
10047 " .jsim/.jsimg/.jsimw path as a JMT simulation\n"
10048 " document, a .pnml path as a place/transition\n"
10049 " net (ISO/IEC 15909-2), and anything else as a\n"
10050 " Network model.json. The three jsim spellings\n"
10051 " name ONE format, as they do in JMT\n"
10052 " -o, --output <fmt> output format: readable | json | jsimg\n"
10053 " (| layers, lqnx for layered models).\n"
10054 " jsimg exports the model as a JMT simulation\n"
10055 " document and solves nothing -- the write half\n"
10056 " of -i jsim|jsimg|jsimw, and the same three\n"
10057 " spellings are accepted. --seed, --samples and\n"
10058 " --tspan set the controls its header carries\n"
10059 " json is honoured by EVERY analysis, not only\n"
10060 " -a avg: each answers under a key named after\n"
10061 " its -a, with the arithmetic and the resolved\n"
10062 " method beside it, and every index inside a\n"
10063 " payload is 0-based against the tables' 1-based\n"
10064 " columns (each payload states its indexBase)\n"
10065 " -s, --solver <name> Network: auto, mva, nc, ctmc, mam, ba, ssa, fluid, uq,\n"
10066 " ldes (the SSJ discrete-event engine, run as a\n"
10067 " subprocess on common/ldes or common/ldes.jar),\n"
10068 " jmt (the Java Modelling Tools engine),\n"
10069 " lqns (its qns methods, the external qnsolver\n"
10071 " layered: auto, ln, ln.mva, ln.comom, lqns,\n"
10072 " ldes (the native in-process LN simulator, a\n"
10073 " sample path of the layered model itself and\n"
10074 " not a decomposition into layers; it takes\n"
10075 " --samples and --seed, and NOT the --ldes-*\n"
10076 " family, which configures the subprocess\n"
10077 " engine that answers -s ldes on a Network)\n"
10078 " environment: env (an Environment model.json)\n"
10079 " -a, --analysis <type> analysis: avg, node, sys, chain, nodechain,\n"
10080 " stage, cache, item, prob, marg, cdf, cdf-passt,\n"
10081 " perct-respt, tran, tran-cdf-respt,\n"
10082 " tran-cdf-passt, tranprob, tranreward,\n"
10083 " reward-value, normconst, gen, states, sample,\n"
10084 " reward, sens, first-passt, odes, statevec,\n"
10085 " var, tranvar, busyperiod,\n"
10086 " jacobian, aoi, internals, bounds, posterior,\n"
10087 " interval. A COMMA LIST runs several in order\n"
10088 " (`-a avg,sys`), emitting one -o json envelope\n"
10089 " per analysis rather than one merged object.\n"
10090 " The JAR CLI's own spellings are accepted as\n"
10091 " aliases -- cdf-respt, prob-sys-aggr, tran-avg,\n"
10092 " generator, reward-steady, all -- and collapse\n"
10093 " onto the arm that already answers them whole:\n"
10094 " -a prob reports getProbSys, getProbSysAggr and\n"
10095 " the per-station pair together, -a sample walks\n"
10096 " all four samplers at once, and -a stage IS\n"
10097 " -a avg on a Network (one implicit stage).\n"
10099 " solver serves is stated by its own refusal;\n"
10100 " ssa serves avg, prob and sample (prob and\n"
10101 " sample run the SERIAL engine whatever -m\n"
10102 " said, since the NRM simulates counts rather\n"
10103 " than the state encoding)\n"
10104 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
10105 " the solver console, a running progress log\n"
10106 " of every solver run\n"
10107 " -d, --seed <n> random seed (SSA, nc --method cftp); default\n"
10108 " 23000. -d is the JAR CLI's spelling\n"
10109 " --warmupfrac <f> SSA: leading fraction of the path discarded\n"
10110 " before the means are taken, in [0,1)\n"
10111 " --pstar <p> fluid: exponent of the p-norm smoothing of the\n"
10112 " drift; without it the hard min() is integrated\n"
10113 " --busyperiod <n,..> -a busyperiod: the orders wanted; default 1\n"
10114 " --busyperiod-subnet <i,..>\n"
10115 " -a busyperiod: the 1-based stations forming the\n"
10116 " subnetwork. Required -- a busy period is defined\n"
10117 " for a NAMED set and no default can choose one\n"
10118 " --method <name> algorithm within the solver\n"
10119 " --samples <n> simulation run length (SSA); default 10000.\n"
10120 " Accepts 1e6 as well as 1000000. A simulation\n"
10121 " figure is only a measurement WITH this number,\n"
10122 " which is why the SSA banner reports it back.\n"
10123 " Also the draw count of nc --method cftp,\n"
10124 " default 1e5, where the draw is the answer.\n"
10125 " --mdd-tol <x> level-iteration tolerance of ctmc --method mdd;\n"
10126 " default 1e-12. NOT --tol: that iteration is an\n"
10127 " inner solve whose fixed point is checked\n"
10128 " against the population invariant at 1e-6, so a\n"
10129 " solver-sized tolerance stops short of it\n"
10130 " --mdd-maxiter <n> coupled sweeps before ctmc --method mdd is\n"
10131 " declared non-convergent; default 500\n"
10132 " --level <n> hierarchy level of the ba pbh/bjbh/cbh/sib\n"
10133 " families; default 2\n"
10134 " --qrf-params <j> JSON (inline or a path) with the QRF blocking\n"
10135 " tables of -s ba --method qrf.bas|qrf.rsrd:\n"
10136 " f, MR, BB, MM, MM1, ZZ and optionally F, the\n"
10137 " fields sn_to_qrf_params assembles. ZM is\n"
10138 " derived from ZZ. There is no default: assuming\n"
10139 " no blocking puts the bound ~31x farther from\n"
10140 " exact, so its absence is refused\n"
10141 " --qrf-alpha <j> JSON (nstations x N) load-dependent scaling of\n"
10142 " the ba qrf.mmi.ld, qrf.mmi.linear and qrf.rsrd\n"
10143 " arms; default all ones\n"
10144 " --tol <x> convergence tolerance (mva, nc, mam, ag, fluid)\n"
10145 " --iter_tol <x> outer-loop tolerance (mva, nc, fluid)\n"
10146 " --iter_max <n> iteration cap (mva, nc, mam, ag, fluid)\n"
10147 " --max-states <n> truncation level of an OPEN agent's queue-length\n"
10148 " dimension (ag), options.config.maxStates;\n"
10149 " default 100. A closed class is bounded by its\n"
10150 " own population instead, and --method inapinf\n"
10151 " ignores the level and solves the open agents on\n"
10152 " the infinite state space\n"
10153 " --fork-join <arm> which fork-join transform the mean-value fixed\n"
10154 " point takes (mva, nc): default|mmt|fjt is the\n"
10155 " MMT transform, ht|heidelberger-trivedi the\n"
10156 " Heidelberger-Trivedi one, which is CLOSED\n"
10157 " models only and a different answer to the same\n"
10158 " model rather than a faster route to one\n"
10159 " --cutoff <n|matrix> open jobs per class in the CTMC state space;\n"
10160 " a matrix is per (station,class), '1,1,0;3,3,0;0,0,3'\n"
10161 " (ctmc); without it the reference's\n"
10162 " ceil(6000^(1/(M*K))) is used and reported.\n"
10163 " Also the level truncation of mam -a prob,\n"
10164 " whose open queue has no bound of its own\n"
10165 " --fj-accuracy <n> FJ_codes truncation C of the queue-length\n"
10166 " difference between the two fork-join\n"
10167 " branches (mam, homogeneous fork-join);\n"
10168 " default 100, larger is more accurate\n"
10169 " --fj-tmode <mode> how that approximation solves for its T\n"
10170 " matrix: NARE (default) or Sylves\n"
10171 " --timescale <mode> auto (default), discrete or continuous: how\n"
10172 " -s mam reads the time scale. auto lets the\n"
10173 " distributions decide; discrete raises rather\n"
10174 " than solve a model that mixes lattice and\n"
10175 " non-lattice laws. The slot is --slotlength\n"
10176 " --tspan <t0>:<t1> transient horizon (ctmc -a tranprob,\n"
10177 " mam -a tran, fluid); a bare <t1> starts at 0.\n"
10178 " For the CTMC and MAM there is no default:\n"
10179 " pi(t) on an unstated horizon is not a\n"
10180 " quantity. For the fluid solver it bounds the\n"
10181 " integration, and is what -s fluid --method kp\n"
10182 " reports its covariance AT\n"
10183 " -n, --node <n> 1-based stateful node a state query is\n"
10184 " labelled by (ctmc -a tranprob, -a sample and\n"
10185 " ssa -a sample), the\n"
10186 " queue mam -a prob reports, or the station\n"
10187 " mva -a marg reports; without it the\n"
10188 " whole network is reported (the MAM queries\n"
10189 " take the model's only Queue)\n"
10190 " -c, --class <r> 1-based job class of mva -a marg; without it\n"
10191 " every class is reported\n"
10192 " NOTE ON THE INDEX BASE: -n and -c are 1-BASED\n"
10193 " here, as every station index this CLI takes\n"
10194 " is, and 0-BASED in jline.cli.LineCLI, which\n"
10195 " indexes as Java does. The short spellings are\n"
10196 " accepted so one command line parses in both,\n"
10197 " but the SAME number names a different node --\n"
10198 " the two bridges (cpp_dispatch, jar_dispatch)\n"
10199 " each convert for their own CLI\n"
10200 " --marg-states <ns> comma-separated job counts the mva -a marg\n"
10201 " curve is evaluated at, the reference's\n"
10202 " state_m; without it each law takes its own\n"
10203 " default range (0..N_r closed, mean + 5 sigma\n"
10204 " Poisson, the 1e-10 tail geometric)\n"
10205 " --notation <form> scalar (default) | matrix, the form the ODE\n"
10206 " export writes (fluid -a odes only)\n"
10207 " --symbolic <b> computer-algebra backend of fluid -a\n"
10208 " jacobian: auto (default, searches for a\n"
10209 " line-sage-rest service), a URL, an image\n"
10210 " name, or none to differentiate locally\n"
10211 " --equilibria also ask the backend for the solutions of\n"
10212 " f(x) = 0 (fluid -a jacobian). Needs a\n"
10213 " backend: solving is not differentiating\n"
10214 " --cdf-algorithm <a> exact (default, pfqn_stdf) | rd\n"
10215 " (pfqn_stdf_heur), how the sojourn law is\n"
10216 " inverted (nc -a cdf only)\n"
10217 " --perm-engine <e> exact (default, Ryser) | spm | bethe | heur |\n"
10218 " huberlaw | adapart, the permanent estimator\n"
10219 " of nc -a sysmarg. The five approximations\n"
10220 " refuse a demand matrix with a zero entry;\n"
10221 " spm is the saddle point, whose cost does not\n"
10222 " grow with the population\n"
10223 " --tran-points <n> points on the uniform transient grid the ENV\n"
10224 " mean-field coupling sums its stage exit\n"
10225 " metrics over (env only); default 1001\n"
10226 " --state <n,...> the ENCODED state row of the node named by\n"
10227 " --node that -a prob asks about, i.e.\n"
10228 " getProb(node, state)'s second argument; without\n"
10229 " it the query is about the model's default\n"
10231 " --events <n> length of ONE sampled trajectory (-a sample);\n"
10232 " default 1000. NOT --samples, which is a\n"
10233 " solver's run length\n"
10234 " --timestep <dt> fixed output step of a transient CTMC solve\n"
10235 " (-a tranprob, -a tranreward), options.timestep\n"
10236 " of ctmc_transient.m; without it the grid is the\n"
10237 " integrator's own adaptive one. It changes WHERE\n"
10238 " the solution is reported, not how it is\n"
10239 " computed: the grid points are read off the same\n"
10241 " --transient-method <m> ode (default) or fau, how the CTMC forward\n"
10242 " equation is advanced over the output grid\n"
10243 " --fau-epsilon <e> fau: probability mass the grid may discard\n"
10244 " --fau-delta <d> fau: occupancy below which a state is dropped\n"
10245 " --rate-sched <j> -s ctmc -a tran: time-varying rates, a JSON array\n"
10246 " (inline or a file) of {station, class, tgrid,\n"
10247 " rates[, nominal]}; station and class by name or\n"
10248 " 1-based index. The rate of that pair follows the\n"
10249 " piecewise-linear schedule, held at its end values\n"
10250 " outside tgrid (options.config.rate_sched)\n"
10251 " --ctmc-tv-ngrid <n> grid size of the --rate-sched propagator (100)\n"
10252 " --percentiles <p,..> levels getPerctRespT is read at (-s mam),\n"
10253 " as fractions (0.9) or percents (90); default\n"
10254 " 0.50,0.90,0.95,0.99, the reference's pers_stored\n"
10255 " --reward-name <nm> which declared reward -a reward-value returns\n"
10256 " the value function of. REQUIRED there and never\n"
10257 " defaulted: two rewards have different value\n"
10258 " functions, and picking one would mislabel it\n"
10259 " --map-env <mode> default (on) | off, whether a model whose ONLY\n"
10260 " unsupported features are MAP/MMPP2/MMAP/MPH is\n"
10261 " solved through a random-environment image of the\n"
10262 " modulating chain instead of being refused. Not a\n"
10263 " -s env knob: it applies to mva, nc and fluid,\n"
10264 " which is where the refusal it replaces comes\n"
10265 " from. off restores that plain refusal\n"
10266 " --map-env-method <m> auto (default) | dec | avg | meanfield, the\n"
10267 " recombination the image is solved with. auto\n"
10268 " picks from the environment timescale; meanfield\n"
10269 " needs a stage backend and is refused by name\n"
10270 " where there is none\n"
10271 " --map-env-maxstages <n> cap on the stages map2renv draws from the\n"
10272 " modulating chain; 0, the default, takes every\n"
10273 " phase. Beyond the JAR, which has no such cap\n"
10274 " -p, --port <n> run as a solve SERVER on this port, speaking\n"
10275 " LineWebSocketServer's protocol: one WebSocket\n"
10276 " text message per connection, its first line the\n"
10277 " comma-separated argument list and its remainder\n"
10278 " the model document; the CLI's output comes back\n"
10279 " as one text message. Plaintext, one connection\n"
10280 " at a time, and -f is refused beside it\n"
10281 " -m, --maxreq <n> quit after serving n requests; without it the\n"
10282 " server runs until interrupted\n"
10283 " -h, --help the short message: the flags a first run needs\n"
10284 " --help-all this message, every option solver by solver\n"
10285 " -V, --version version string\n"
10286 " --install environment check: report which optional backends\n"
10287 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
10289 "Layered models (-i lqnx|xml) additionally take:\n"
10290 " --layer-solver <s> solver run in each layer: mva (default)|nc|\n"
10291 " fluid|ssa, the C++ spelling of the reference's\n"
10292 " factory argument: LN(model, @(m) MVA(m))\n"
10293 " against LN(model, @(m) Fluid(m)). They converge\n"
10294 " to DIFFERENT fixed points, not to the same one\n"
10295 " by different routes, because each layer's\n"
10296 " results feed the next outer iteration's demands.\n"
10297 " fluid is double only and refuses a layer with a\n"
10298 " fork; ssa is NOISY, so the outer loop switches\n"
10299 " to the Robbins-Monro / Polyak-Ruppert controller\n"
10300 " --method <name> the LN update: default | moment3 | mw.upper |\n"
10301 " mw.lower. moment3 fits an APH to each layer's\n"
10302 " response-time CDF and convolves along the entry,\n"
10303 " which is what makes -a cdf possible; mw.*\n"
10304 " reports Majumdar-Woodside box BOUNDS on\n"
10305 " throughput and processor utilization and solves\n"
10306 " no layer at all (every other metric is NaN)\n"
10307 " --ln-transient <m> coupled (default) | decoupled, how -a tran\n"
10308 " couples the layers. decoupled freezes the\n"
10309 " inter-layer demands at the fixed point; coupled\n"
10310 " relaxes time-varying demands through the fluid\n"
10311 " rate schedule until the trajectories settle\n"
10312 " --ln-transient-channels <c> both (default) | thinkt | callservt,\n"
10313 " which inter-layer coupling the relaxation\n"
10314 " injects, for isolating one channel's share\n"
10315 " --sens-method <m> auto (default) | exact | fd, the branch each\n"
10316 " LAYER's sensitivity table takes (-a sens);\n"
10317 " the same three under -s nc -a sens\n"
10318 " --sens-scheme <s> forward (default) | central, the difference\n"
10319 " quotient of the fd branch\n"
10320 " --sens-step <h> relative rate perturbation of the fd branch,\n"
10321 " in (0,1); default 1e-4, or 1e-2 for ssa layers\n"
10322 " --no-interlocking disable the interlocking correction\n"
10323 " --interlock-method <m> ilrate (default) | refpath | none, how\n"
10324 " the interlock is tracked; refpath merges the\n"
10325 " callers that are one reference task's\n"
10326 " customers into one chain (srvn.cs only, it\n"
10327 " falls back to ilrate elsewhere, with a warning)\n"
10328 " --interlock-maxpaths <n> refpath refuses a layer carrying more\n"
10329 " reference routes into it; default 32\n"
10330 " --interlock-refpath-scope <s> merging (default) | all, whether\n"
10331 " refpath also rewrites a lone-caller layer\n"
10332 " --repeat <k> re-solve k times, report the best wall clock\n"
10333 " -o layers dump every layer's stations, classes, rates\n"
10334 " and routing instead of the AvgTable\n"
10335 " -a takes avg (getAvgTable), tran (getTranAvg, needs --tspan and fluid\n"
10336 " layers), sens (getSensitivityTable) and cdf (getCdfRespT, moment3).\n"
10337 " --iter_max and --iter_tol set the outer LN loop; --tol does not apply,\n"
10338 " and neither does any Network solver token.\n"
10340 "The external LQNS binary (-s lqns) additionally takes:\n"
10341 " --method <name> default | lqns | srvn | exactmva |\n"
10342 " srvn.exactmva | sim | lqsim | lqns.default.\n"
10343 " sim and lqsim run lqsim, the SIMULATOR;\n"
10344 " lqns.default is lqns with no pragma at all,\n"
10345 " which is a different fixed point and not a\n"
10346 " synonym for default\n"
10347 " --multiserver <p> conway|rolia|zhou|suri|reiser|schmidt|default\n"
10348 " (= rolia), the -Pmultiserver= pragma. Not\n"
10349 " passed to lqsim, which has no MVA to configure\n"
10350 " --samples <n> lqsim run length (-A); default 10000\n"
10351 " --timeout <s> kill the child after s seconds; without it the\n"
10353 " --keep keep the working directory with model.lqnx and\n"
10354 " model.lqxo instead of removing it\n"
10355 " --verbose echo the command line and the binary's output\n"
10356 " --remote[-url <u>] solve on a host running lqns-rest instead of\n"
10357 " locally; -url implies --remote. LINE ships no\n"
10358 " LQNS binary, so this is the other way to reach\n"
10360 " -a takes avg only: lqns computes no transient, no sensitivity and no\n"
10361 " response-time distribution. QLen is the element utilization, Util its\n"
10362 " processor utilization per server, RespT its phase-1 service time;\n"
10363 " ResidT and ArvR print NaN because lqns reports neither.\n"
10365 "The external qnsolver binary (-s lqns on a Network) additionally takes:\n"
10366 " --method <name> default | qns (both = rolia) | qns.conway |\n"
10367 " qns.rolia | qns.zhou | qns.reiser | qns.suri |\n"
10368 " qns.schmidt. The multiserver approximation,\n"
10369 " passed as qnsolver -m and only\n"
10370 " when the model HAS a multiserver station. suri\n"
10371 " and schmidt are lqns approximations: they reach\n"
10372 " the tool only on the non-product-form closed\n"
10373 " branch below, and a multiserver model on the\n"
10374 " qnsolver branch refuses them by name\n"
10375 " --multiserver <p> the same choice under its config spelling;\n"
10376 " --method wins when it names one\n"
10377 " --timeout <s> kill the child after s seconds; without it the\n"
10379 " --keep keep the working directory with model.jmva and\n"
10380 " result.jmva instead of removing it\n"
10381 " -a takes avg only. The model is marshalled to the JMVA interchange\n"
10382 " format at CHAIN level and the chain results are de-aggregated back to\n"
10383 " classes, so only Queue, Delay and Source stations are expressible; any\n"
10384 " other station is refused rather than dropped. A closed model that is\n"
10385 " NOT product-form is converted with QN2LQN and delegated to lqns, as the\n"
10386 " reference does, so it also needs the lqns binary; class priorities are\n"
10387 " outside that conversion and are refused. --arith is double only, since\n"
10388 " the results arrive as the text an external binary printed.\n"
10390 "The Java Modelling Tools wrapper (-s jmt) additionally takes:\n"
10391 " --jmt-replications <n> independent JSIM runs the transient ensemble\n"
10392 " of -a tran and -a tranprob averages over\n"
10393 " (options.config.replications, default 10),\n"
10394 " replication k seeded seed+k-1. A single path\n"
10395 " is NOT E[N](t); --method jsim is one run\n"
10397 "Discrete-event simulation (-s ldes) additionally takes:\n"
10398 " --ldes-tranfilter <f> warmup filter: mser5 (default), fixed, none\n"
10399 " --ldes-warmupfrac <x> fraction the fixed filter discards (0.2)\n"
10400 " --ldes-cimethod <m> CI estimator: obm (default), bm, spectral,\n"
10402 " --ldes-cnvgon stop on relative precision instead of on\n"
10403 " the --samples budget\n"
10404 " --ldes-cnvgtol <x> that precision target (0.05); implies\n"
10406 " --slotted run the analytical solver on a discrete\n"
10407 " (slotted) time scale; SolverNC routes to the\n"
10408 " discrete-time product form and refuses a model\n"
10410 " --slotlength <x> the slot in model time units; implies --slotted\n"
10411 " --ldes-slotted run on a discrete (slotted) time scale; a\n"
10412 " sample off the lattice is an error, never\n"
10414 " --ldes-slotlength <x> the slot (1.0); implies --ldes-slotted\n"
10415 " --ldes-replications <n> independent runs, averaged. A single path\n"
10416 " is NOT E[N](t): -a tran over an ensemble\n"
10417 " mean needs this\n"
10418 " --ldes-numthreads <n> workers for those replications\n"
10419 " --ldes-maxtime <s> wall-clock budget; the engine stops early\n"
10420 " and reports stopping=max_time\n"
10421 " --ldes-initsol <v,..> warm-start placement, station-major\n"
10422 " [st0_cl0, st0_cl1, ...]. Add --ldes-tranfilter\n"
10423 " fixed --ldes-warmupfrac 0 to reproduce\n"
10424 " initFromSolver, which assumes the placement\n"
10425 " is already a steady state\n"
10426 " --ldes-rest-url <u> solve on an LDES REST server instead of a\n"
10427 " local binary; same wire format, same numbers\n"
10428 " The model.json is forwarded to the engine BYTE FOR BYTE, so a model\n"
10429 " this port cannot itself parse (a cache with retrieval, an SPN, a\n"
10430 " polling server) is simulated exactly as the MATLAB and Python clients\n"
10431 " simulate it. -a avg, tran, cdf (the empirical response-time law),\n"
10432 " sample, reward and prob (the histogram's residence time at the declared\n"
10433 " per-class counts; --node with --state overrides one station) are\n"
10434 " served. --arith is double only.\n"
10436 "Uncertainty quantification (-s uq) additionally takes:\n"
10437 " --uq-solver <s> the engine run at each design point: mva, nc,\n"
10438 " mam, ba, ctmc, fluid or ssa. REQUIRED: UQ\n"
10439 " computes nothing itself, and defaulting it\n"
10440 " would attribute the numbers to an engine the\n"
10441 " caller never chose\n"
10442 " A model whose service or arrival process is a Prior is a FAMILY of\n"
10443 " models. UQ discretizes each Prior, solves the tensor product of the\n"
10444 " alternatives, and reports the prior-weighted expectation. Under -s uq\n"
10445 " three flags describe the DESIGN and not the engine: --method is\n"
10446 " quadrature (default, and the alias of discrete) or montecarlo,\n"
10447 " --samples the nodes per continuous Prior (11), --seed the Monte Carlo\n"
10448 " stream. The stage solver therefore keeps its own sample count and its\n"
10449 " own seed; --tol, --iter_tol, --iter_max and --cutoff pass through to\n"
10450 " it, since UQ has no convergence of its own. -a posterior prints every\n"
10451 " design point, its weight and the means it substituted, which is what\n"
10452 " says whether the expectation averaged two nearby models or two very\n"
10453 " different ones. Every other solver REFUSES a model carrying a Prior\n"
10454 " rather than lowering it to its mixture moments.\n"
10455 " -a interval answers the OTHER epistemic question, in which a\n"
10456 " parameter is bounded but not distributed: it drops the weights and\n"
10457 " keeps the endpoints. On a single-class closed model of LI\n"
10458 " single-server queues and delays it is the EXACT hull of MVA over the\n"
10459 " demand box (2*(m+2) MVA calls, no design solved at all); otherwise it\n"
10460 " falls back to the range over the solved design points, which for a\n"
10461 " continuous Prior lies strictly inside the true range. The table says\n"
10462 " which, and the fallback warns on stderr: a range that is not an\n"
10463 " enclosure must not read like one.\n"
10465 "Random environments (-s env) read an Environment model.json, whose\n"
10466 " stages each hold a Network and whose transitions carry the stage\n"
10467 " holding times. The stages are solved TRANSIENTLY and coupled: each\n"
10468 " stage starts from the queue lengths the previous one left, and the\n"
10469 " reported means are the per-stage sojourn averages blended by the\n"
10470 " environment probabilities. --method selects the coupling: meanfield\n"
10471 " (default, the reference's, carries the marginal means across a\n"
10472 " switch) or statevec|blend (carries the whole joint distribution).\n"
10473 " meanfield solves each stage with the fluid transient and is double\n"
10474 " only; statevec uniformizes a CTMC and takes the whole --arith ladder.\n"
10475 " --method avg|dec asks instead for a closed-form limit, which carries\n"
10476 " nothing across a switch and iterates nothing: avg solves ONE model\n"
10477 " whose modulated rates are their probEnv-weighted averages (exact as\n"
10478 " the environment gets fast), dec solves each stage in steady state and\n"
10479 " blends by probEnv (exact as it gets slow). A model with an\n"
10480 " environment-declared node breakdown is read from the nodeFailures\n"
10481 " block, in the expanded or the one-stage macro form.\n"
10482 " --tspan bounds the stage horizon and --tran-points its grid; --iter_tol\n"
10483 " and --iter_max drive the fixed point. RespT and ArvR print as nan\n"
10484 " because ENV computes neither -- the reference returns them as NaN too,\n"
10485 " and ResidT carries QLen/Tput.\n"
10487 "Additions specific to this port:\n"
10488 " --arith <mode> double (default) | exact | real:<digits>\n"
10489 " --list-api list the API functions ported so far\n"
10490 " --generate <spec> draw a random model and print it, solving\n"
10491 " nothing. <spec> is a JSON object inline, a\n"
10492 " path to one, or `-` for standard input.\n"
10493 " {\"kind\":\"network\"} draws a flat model and\n"
10494 " prints the line-model JSON document; its keys\n"
10495 " are seed, name, queues, delays, openClasses,\n"
10496 " closedClasses, schedStrat, routingStrat,\n"
10497 " distribution, cclassJobLoad,\n"
10498 " varyingServiceRates, multiServerQueues,\n"
10499 " randomCSNodes, multiChainCS and topology\n"
10500 " (rand|cyclic). A `delays` of -1, the default,\n"
10501 " lets the generator split the stations itself.\n"
10502 " {\"kind\":\"layered\"} draws an LQN and prints\n"
10503 " the .lqnx document; its keys are seed, name,\n"
10504 " clients, levels, tasks, processors,\n"
10505 " populationRange, thinkTimeRange,\n"
10506 " taskMultiRange, procMultiRange,\n"
10507 " hostDemandRange, synchCallRange (each a\n"
10508 " [lo,hi] pair), taskInfProbability and\n"
10509 " procInfProbability. Both draw from\n"
10510 " java.util.Random at `seed`, so a seeded spec\n"
10511 " reproduces; --arith is not read, since a draw\n"
10512 " is a double whatever the model is solved at.\n"
10513 " --api <name> invoke one API function directly\n"
10514 " --args <path> JSON arguments for --api; stdin when omitted\n"
10516 "Solvers and what they honour. Every model-solving solver reads -a avg,\n"
10517 "-f/-i and --method; nothing else is wired, so tolerances, iteration\n"
10518 "caps, seeds and sample counts keep their SolverOptions defaults rather\n"
10519 "than being invented here. mva and nc additionally read -a prob -- mva\n"
10520 "fits a binomial to its own means, nc returns the exact product-form\n"
10521 "probability, so the two disagree by construction. mva also reads -a\n"
10522 "marg, @SolverMVA's getProbMarg: P(n jobs of class r at station i) for\n"
10523 "every (station, class) pair, narrowed by --node / --class and evaluated\n"
10524 "at --marg-states. A closed class takes the Schmidt binomial fitted to\n"
10525 "Q(i,r); an open one takes the station's exact BCMP marginal (Poisson at\n"
10526 "an infinite server, multinomial-geometric at a queue). Both solvers read\n"
10527 "-a normconst, getProbNormConstAggr: nc reports the constant its solve\n"
10528 "already formed, mva RE-ENTERS its analyzer at method='exact', since only\n"
10529 "the exact recursion carries a G -- a model whose branch forms none, an\n"
10530 "open or mixed one above all, reports nan, as the reference does. nc also\n"
10532 "@SolverNC's getCdfRespT: the whole response-time law per (station,\n"
10533 "class) on one logarithmic grid, FCFS stations only, with\n"
10534 "--cdf-algorithm exact (pfqn_stdf, the default) or rd (pfqn_stdf_heur).\n"
10535 "nc reads -a sens as well, @NetworkSolver's getSensitivityTable: the\n"
10536 "derivative of each row's means with respect to its own service rate,\n"
10537 "selected with --sens-method / --sens-scheme / --sens-step. NC is one of\n"
10538 "the two engines whose exact branch differentiates the product-form\n"
10539 "recursion analytically, so auto takes it wherever the model is in its\n"
10540 "scope (single-server queues plus delays, not mixed) and falls back to\n"
10541 "finite differences elsewhere. NOT the -s ctmc -a sens analysis, which\n"
10542 "is getSensitivityRanking, a ranking of rate perturbations and not a\n"
10543 "table of derivatives.\n"
10544 "-a node is @NetworkSolver's getAvgNodeTable and is served by mva, nc,\n"
10545 "mam, ba and ctmc, the model solvers whose runner returns the station\n"
10546 "AvgResult it is built from. It is a DIFFERENT INDEX SPACE from -a avg,\n"
10547 "not a relabelling: the AvgTable has one row per STATION, so a\n"
10548 "ClassSwitch, Router, Fork, Join or Sink never appears in it, yet jobs\n"
10549 "flow through all of them. QLen, Util, RespT and ResidT are the station\n"
10550 "numbers scattered to their node indices and zero elsewhere -- a node\n"
10551 "that is not a station holds no jobs -- while ArvR and Tput are\n"
10552 "recomputed for every node by sn_get_node_arvr_from_tput and\n"
10553 "sn_get_node_tput_from_tput. The reference's finite-capacity-region\n"
10554 "pseudo-node rows are NOT emitted: this port's AvgResult carries no\n"
10555 "per-region queue length or utilization to fill them with.\n"
10557 "auto picks the engine\n"
10558 "with chooseSolverHeur and prints the name it picked; a branch selecting\n"
10559 "JMT or LDES refuses by name rather than substituting another. ctmc reads\n"
10560 "--cutoff and ports -a avg, prob, gen, states, tranprob, sample, reward,\n"
10561 "cdf, first-passt and sens, which are @SolverCTMC's\n"
10562 "getProbSys/getProbSysAggr and the per-station getProb/getProbAggr,\n"
10563 "getInfGen, getStateSpace, getTranProbSysAggr, sampleSys, getAvgReward,\n"
10564 "getCdfRespT, getCdfFirstPassT (state sets via --passage-from and\n"
10565 "--passage-into, as 1-based space rows '3,5' or state rows '0,2;1,1'),\n"
10566 "first-passt-moments (-a firstpasstmom, the same two sets plus\n"
10567 "--passage-orders: exact moments by one linear solve per order, so a\n"
10568 "variance or a skewness costs no truncated curve)\n"
10569 "and getSensitivityRanking. Its --method also takes mdd, which holds the\n"
10570 "reachable set in a decision diagram and solves K coupled level-CTMCs\n"
10571 "instead of the |S|-state generator; it serves -a avg only, having no\n"
10572 "explicit chain to answer the rest from. nc's --method also takes cftp /\n"
10573 "cftp.approx, which draw iid states from the exact stationary law of a\n"
10574 "closed single-class product-form network by coupling from the past;\n"
10575 "they serve -a avg only and their rows carry Monte Carlo error.\n"
10576 "mam ports -a avg,\n"
10577 "prob, cdf, tran and internals, which are @SolverMAM's getProb and\n"
10578 "getProbMarg (the joint (level, phase) law of the queue and its\n"
10579 "per-class marginals, truncated at --cutoff when the model is open),\n"
10580 "getCdfRespT with getPerctRespT beside it, getTranAvg over --tspan (the\n"
10581 "reference forces method ldqbd there, so --method does not select it),\n"
10582 "and getMAMResult, the M/G/1-type internals of a single queue. fluid\n"
10583 "additionally reads -a tran, -a prob, -a cdf, -a var and -a aoi, which\n"
10584 "are @SolverFLD's getTranAvg (the metrics along the trajectory, over\n"
10585 "--tspan or, without one, the horizon the reference's own adaptive loop\n"
10586 "converges at), getProbAggr (a law FITTED to the fluid means, so it does\n"
10587 "not agree with the mva or nc answer by construction), getCdfRespT (the\n"
10588 "response-time law per station and class, read off a marked-fluid\n"
10589 "integration started from the steady state), getMoments/getTranAvgVar\n"
10590 "and getAvgAoI with getCdfAoI beside it -- the last needing method mfq\n"
10591 "and the Source/Queue/Sink topology the age laws are defined for. It\n"
10592 "further reads -a odes, which is\n"
10593 "@SolverFLD/exportODEs, and --notation for the form it writes, and -a\n"
10594 "jacobian, which is @SolverFLD/getJacobian: d f_i / d x_j of the drift,\n"
10595 "differentiated locally over the structure of the system, with the\n"
10596 "equilibria beside it under --equilibria, which needs the\n"
10597 "line-sage-rest backend --symbolic names. Only the smooth methods have\n"
10598 "a Jacobian: min(n,S) has none where the regime switches, so the\n"
10599 "min-scaled drifts are refused by the factor that carries the kink.\n"
10600 "nc, ba and ctmc run\n"
10601 "under every --arith backend, nc's cftp method excepted: its sampler\n"
10602 "works in the log domain, so it refuses 'exact' by name rather than\n"
10603 "answering in a field it does not live in. mdd does run under 'exact'\n"
10604 "(its level solve drops Householder for a rational least squares there),\n"
10605 "but the LEVEL AGGREGATION is still an approximation away from product\n"
10606 "form: exact arithmetic pins the fixed point, not the model. mam is\n"
10607 "double only (its phase-type fitter\n"
10608 "needs transcendental arithmetic) and so is ssa (its sample path is\n"
10609 "generated from exponential clocks) and fluid (LSODA). ba reports a\n"
10610 "BOUND, not an estimate, and ssa a simulation carrying Monte Carlo\n"
10611 "error; neither is comparable with an exact solver except as such.\n"
10613 "Arithmetic: 'exact' computes in arbitrary-precision rationals and\n"
10614 "reports numerator and denominator alongside the double value; 'real'\n"
10615 "computes in fixed high-precision binary floating point, at 50, 100 or\n"
10616 "200 digits (a request in between is rounded up to the next tier).\n"
10618 "--api arguments are a JSON object keyed by the MATLAB parameter names,\n"
10619 "e.g. {\"L\": [[0.6,0.4]], \"N\": [2,1], \"Z\": [1,0.5]}: a 2-D array is a\n"
10620 "matrix (row-major), a 1-D array a row vector, a bare number a scalar.\n"
10621 "A JSON number is read as its shortest round-tripping decimal, so 0.6 is\n"
10622 "3/5 in exact arithmetic; pass a string such as \"1/3\" for anything else.\n",
10639bool install_check() {
10640 bool has_warnings =
false;
10645 const auto warn = [&](
const std::string& text) {
10646 std::fflush(stdout);
10647 std::fprintf(stderr,
"%s\n", text.c_str());
10648 std::fflush(stderr);
10649 has_warnings =
true;
10652 std::printf(
"Checking LINE (C++)...\n");
10653 std::printf(
" line-cli %s\n", kVersion);
10655 std::printf(
"Checking Java runtime (JMT wrapper)...\n");
10656 const std::string java = line::jmt::detail::find_java();
10657 if (java.empty()) {
10658 warn(
"WARNING: no Java runtime was found in LINE_JAVA, JAVA_HOME or on PATH, so the "
10659 "JMT wrapper (-s jmt) cannot run. Install a JRE 8 or later.");
10661 std::printf(
" %s\n", java.c_str());
10664 std::printf(
"Checking JMT...\n");
10665 const std::string jmt_dir = line::jmt::detail::jmt_jar_path();
10666 if (jmt_dir.empty()) {
10672 warn(
"WARNING: JMT.jar was not found, this is required by the JMT wrapper. Download it "
10673 "from https://line-solver.sourceforge.net/latest/JMT.jar into common/, or point "
10674 "LINE_JMT_DIR at the folder holding it, or set LINE_JMT_DOWNLOAD=1 to let the first "
10675 "JMT solve fetch and verify it.");
10677 std::printf(
" %s/JMT.jar\n", jmt_dir.c_str());
10680 std::printf(
"Checking LQNS...\n");
10683 if (banner.empty())
10684 warn(
"WARNING: lqns is not installed, this is required by the LQNS wrapper (-s lqns) "
10685 "for layered models. Download it at: https://github.com/layeredqueuing/dist");
10687 warn(
"WARNING: the installed lqns is too old for LINE, which needs release 6 or "
10688 "later; it reports '" + banner +
"'. Upgrade it from: "
10689 "https://github.com/layeredqueuing/dist");
10694 std::printf(
"Checking qnsolver...\n");
10696 warn(
"WARNING: qnsolver is not installed, this is required by the qns methods of the "
10697 "LQNS wrapper (-s lqns on a Network). "
10698 "It ships with LQNS: https://github.com/layeredqueuing/dist");
10700 std::printf(
"Checking symbolic backend (line-sage-rest)...\n");
10702 warn(std::string(
"WARNING: Docker is not available, so the SageMath symbolic backend "
10703 "cannot start. It is the only computer algebra system this edition "
10704 "reaches and is required by the symbolic methods of "
10705 "SolverCTMC/SolverFluid. Install Docker, then run: "
10708 warn(std::string(
"WARNING: the line-sage-rest image is not present locally, this may be "
10709 "required by some LINE methods. Pull it with: "
10713 std::printf(
"Completed. LINE has warnings.\n");
10715 std::printf(
"Success. LINE is ready to use.\n");
10716 return !has_warnings;
10721 std::printf(
"%-24s %-8s %-24s %s\n",
"function",
"domain",
"arithmetic",
"ported from");
10722 for (
const auto& e :
reg) {
10724 for (std::size_t k = 0; k < e.arith.size(); ++k) {
10725 if (k) modes +=
",";
10728 std::printf(
"%-24s %-8s %-24s %s\n", e.name.c_str(), e.domain.c_str(), modes.c_str(),
10729 e.reference.c_str());
10731 std::printf(
"\n%zu of ~480 API functions ported.\n",
reg.size());
10741 std::string source;
10742 if (path.empty()) {
10743 source =
"standard input";
10744 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
10746 source =
"'" + path +
"'";
10747 std::ifstream in(path.c_str());
10749 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
10752 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
10753 throw line::InputError(
"no --api arguments given: pass --args <path> or a JSON object on "
10754 "standard input (read from " +
10757 return line::reg::Json::parse(text);
10758 }
catch (
const line::reg::Json::parse_error& e) {
10759 throw line::InputError(
"malformed --api arguments in " + source +
": " + e.what());
10774 std::string source;
10775 const std::string::size_type first = arg.find_first_not_of(
" \t\r\n");
10776 if (first != std::string::npos && arg[first] ==
'{') {
10777 source =
"the --generate argument";
10779 }
else if (arg.empty() || arg ==
"-") {
10780 source =
"standard input";
10781 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
10783 source =
"'" + arg +
"'";
10784 std::ifstream in(arg.c_str());
10785 if (!in)
throw line::InputError(
"cannot open the --generate spec file " + source);
10786 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
10788 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
10789 throw line::InputError(
"no --generate specification given: pass a JSON object inline, a "
10790 "path to one, or `-` for standard input (read from " +
10793 return line::reg::Json::parse(text);
10794 }
catch (
const line::reg::Json::parse_error& e) {
10795 throw line::InputError(
"malformed --generate specification in " + source +
": " + e.what());
10801 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10803 if (!v.is_number())
10804 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a number");
10805 const double d = v.get<
double>();
10806 if (d != std::floor(d))
10807 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a whole number");
10808 return static_cast<long>(d);
10812double gen_real(
const line::reg::Json& j,
const char* key,
double dflt) {
10813 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10815 if (!v.is_number())
10816 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a number");
10817 return v.get<
double>();
10822 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10824 if (!v.is_boolean())
10825 throw line::InputError(std::string(
"--generate: '") + key +
"' must be true or false");
10826 return v.get<
bool>();
10830std::string gen_str(
const line::reg::Json& j,
const char* key,
const std::string& dflt) {
10831 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10833 if (!v.is_string())
10834 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a string");
10835 return v.get<std::string>();
10840 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10842 if (!v.is_array() || v.size() != 2 || !v[0].is_number() || !v[1].is_number())
10844 "' must be a two-element array of numbers");
10854void gen_check_keys(
const line::reg::Json& j,
const char*
const* allowed, std::size_t n) {
10855 for (line::reg::Json::const_iterator it = j.begin(); it != j.end(); ++it) {
10857 for (std::size_t k = 0; k < n && !ok; ++k) ok = it.key() == allowed[k];
10860 for (std::size_t k = 0; k < n; ++k) {
10861 if (k) names +=
", ";
10862 names += allowed[k];
10865 "'; this kind reads: " + names);
10887std::string normalize_analysis(
const std::string& a) {
10889 if (a ==
"cdf-respt" || a ==
"cdfrespt")
return "cdf";
10890 if (a ==
"cdf-passt" || a ==
"cdfpasst")
return "cdfpasst";
10891 if (a ==
"first-passt" || a ==
"cdf-firstpasst" || a ==
"cdffirstpasst")
return "firstpasst";
10892 if (a ==
"first-passt-moments" || a ==
"firstpasst-moments" || a ==
"firstpasstmoments")
10893 return "firstpasstmom";
10894 if (a ==
"perct-respt" || a ==
"perctrespt")
return "perct";
10895 if (a ==
"tran-avg" || a ==
"tranavg")
return "tran";
10896 if (a ==
"tran-cdf-respt" || a ==
"trancdfrespt")
return "trancdf";
10897 if (a ==
"tran-cdf-passt" || a ==
"trancdfpasst")
return "trancdfpasst";
10898 if (a ==
"tran-prob" || a ==
"tranprob-sys-aggr")
return "tranprob";
10899 if (a ==
"generator")
return "gen";
10900 if (a ==
"state-space" || a ==
"statespace")
return "states";
10901 if (a ==
"reward-steady" || a ==
"rewardsteady")
return "reward";
10902 if (a ==
"reward-value" || a ==
"rewardvalue")
return "rewardvalue";
10903 if (a ==
"node-chain" || a ==
"node-chain-table")
return "nodechain";
10908 if (a ==
"prob-aggr" || a ==
"prob-sys" || a ==
"prob-sys-aggr")
return "prob";
10909 if (a ==
"prob-marg" || a ==
"probmarg")
return "marg";
10910 if (a ==
"prob-sys-marg" || a ==
"probsysmarg" || a ==
"sys-marg")
return "sysmarg";
10911 if (a ==
"sample-aggr" || a ==
"sample-sys" || a ==
"sample-sys-aggr")
return "sample";
10917 if (a ==
"stage")
return "avg";
10922std::vector<std::string> analysis_list(
const std::string& spec) {
10923 std::vector<std::string> out;
10924 std::size_t at = 0;
10925 while (at <= spec.size()) {
10926 const std::size_t comma = spec.find(
',', at);
10928 spec.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
10930 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.front())))
10931 tok.erase(tok.begin());
10932 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.back())))
10935 throw line::InputError(
"-a takes a comma-separated list of analyses and one entry of '" +
10936 spec +
"' is empty");
10940 if (normalize_analysis(tok) ==
"all") {
10941 out.push_back(
"avg");
10942 out.push_back(
"sys");
10944 out.push_back(normalize_analysis(tok));
10946 if (comma == std::string::npos)
break;
10949 if (out.empty())
throw line::InputError(
"-a takes at least one analysis");
10954 std::string file, input =
"json", output =
"readable", solver =
"auto", analysis =
"avg";
10955 std::string arith =
"double", api, args;
10956 bool help =
false, help_all =
false, version =
false, list =
false;
10966 bool find_solver =
false, find_solver_all =
false;
10967 std::string find_solver_metric;
10969 bool install =
false;
10980 std::string generate;
10981 bool generate_given =
false;
10990 bool input_given =
false;
11005bool has_jsim_extension(
const std::string& file) {
11006 const std::string::size_type
dot = file.find_last_of(
'.');
11007 if (dot == std::string::npos)
return false;
11008 std::string ext = file.substr(dot + 1);
11009 for (std::size_t i = 0; i < ext.size(); ++i)
11010 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11011 return ext ==
"jsim" || ext ==
"jsimg" || ext ==
"jsimw";
11015bool has_pnml_extension(
const std::string& file) {
11016 const std::string::size_type
dot = file.find_last_of(
'.');
11017 if (dot == std::string::npos)
return false;
11018 std::string ext = file.substr(dot + 1);
11019 for (std::size_t i = 0; i < ext.size(); ++i)
11020 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11021 return ext ==
"pnml";
11025bool has_lqn_extension(
const std::string& file) {
11026 const std::string::size_type
dot = file.find_last_of(
'.');
11027 if (dot == std::string::npos)
return false;
11028 std::string ext = file.substr(dot + 1);
11029 for (std::size_t i = 0; i < ext.size(); ++i)
11030 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11031 return ext ==
"lqnx" || ext ==
"xml";
11034Options parse_args(
int argc,
char** argv) {
11036 for (
int i = 1; i < argc; ++i) {
11037 std::string a = argv[i];
11038 auto next = [&](
const char* what) -> std::string {
11039 if (i + 1 >= argc)
throw line::InputError(std::string(
"missing value after ") + what);
11042 if (a ==
"-h" || a ==
"--help") o.help =
true;
11043 else if (a ==
"--help-all" || a ==
"--help-full") o.help_all =
true;
11044 else if (a ==
"-V" || a ==
"--version") o.version =
true;
11045 else if (a ==
"--install") o.install =
true;
11046 else if (a ==
"--list-api") o.list =
true;
11047 else if (a ==
"--generate") {
11050 o.generate_given =
true;
11053 if (i + 1 < argc && (argv[i + 1][0] !=
'-' || argv[i + 1][1] ==
'\0'))
11054 o.generate = argv[++i];
11056 else if (a ==
"--find-solver" || a ==
"--find-method" || a ==
"--help-model") {
11057 o.find_solver =
true;
11060 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
11061 }
else if (a ==
"--find-solver-all" || a ==
"--find-method-all") {
11062 o.find_solver =
true;
11063 o.find_solver_all =
true;
11064 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
11066 else if (a ==
"-f" || a ==
"--file") o.file = next(
"-f");
11067 else if (a ==
"-i" || a ==
"--input") { o.input = next(
"-i"); o.input_given =
true; }
11068 else if (a ==
"-o" || a ==
"--output") o.output = next(
"-o");
11069 else if (a ==
"-s" || a ==
"--solver") o.solver = next(
"-s");
11070 else if (a ==
"-a" || a ==
"--analysis") o.analysis = next(
"-a");
11071 else if (a ==
"--arith") o.arith = next(
"--arith");
11072 else if (a ==
"-p" || a ==
"--port") {
11073 const std::string v = next(
"-p");
11074 const long n = std::atol(v.c_str());
11075 if (n < 1 || n > 65535)
11076 throw line::InputError(
"-p takes a TCP port in 1..65535 (got '" + v +
"')");
11077 o.port =
static_cast<int>(n);
11078 }
else if (a ==
"-m" || a ==
"--maxreq") {
11079 const std::string v = next(
"-m");
11080 const long n = std::atol(v.c_str());
11083 "-m is the number of requests the server serves before quitting and must be "
11084 "positive; omit it to serve indefinitely (got '" + v +
"')");
11085 o.maxreq =
static_cast<int>(n);
11087 else if (a ==
"--api") o.api = next(
"--api");
11088 else if (a ==
"--args") o.args = next(
"--args");
11089 else if (a ==
"--method") o.knobs.method = next(
"--method");
11090 else if (a ==
"--qrf-params") o.knobs.qrf_params = next(
"--qrf-params");
11091 else if (a ==
"--qrf-alpha") o.knobs.qrf_alpha = next(
"--qrf-alpha");
11092 else if (a ==
"--level") {
11093 const std::string v = next(
"--level");
11094 const int lv = std::atoi(v.c_str());
11095 if (lv < 1)
throw line::InputError(
"--level must be a positive integer (got '" + v +
"')");
11096 o.knobs.level = lv;
11098 else if (a ==
"--samples") {
11099 const std::string v = next(
"--samples");
11100 const double d = std::atof(v.c_str());
11102 throw line::InputError(
"--samples must be a positive count (got '" + v +
"')");
11103 o.knobs.samples =
static_cast<std::size_t
>(d);
11104 }
else if (a ==
"-d" || a ==
"--seed") {
11105 const std::string v = next(
"--seed");
11106 o.knobs.seed = std::strtoul(v.c_str(),
nullptr, 10);
11107 if (o.knobs.seed == 0)
11108 throw line::InputError(
"--seed must be a positive integer (got '" + v +
"')");
11109 }
else if (a ==
"--warmupfrac") {
11110 const std::string v = next(
"--warmupfrac");
11111 const double f = std::atof(v.c_str());
11112 if (!(f >= 0.0 && f < 1.0))
11114 "--warmupfrac is the fraction of the path discarded before the means are "
11115 "taken and must lie in [0,1) (got '" + v +
"')");
11116 o.knobs.warmupfrac = f;
11117 }
else if (a ==
"--pstar") {
11118 const std::string v = next(
"--pstar");
11119 const double ps = std::atof(v.c_str());
11122 "--pstar is the exponent of the fluid p-norm smoothing and must be positive "
11123 "(got '" + v +
"')");
11124 o.knobs.pstar = ps;
11125 }
else if (a ==
"--busyperiod" || a ==
"--busyperiod-subnet") {
11128 const bool orders = (a ==
"--busyperiod");
11129 const std::string v = next(orders ?
"--busyperiod" :
"--busyperiod-subnet");
11130 std::vector<std::size_t>& into = orders ? o.knobs.busy_orders : o.knobs.busy_subnet;
11131 std::size_t at = 0;
11132 while (at <= v.size()) {
11133 const std::size_t comma = v.find(
',', at);
11134 const std::string tok =
11135 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11136 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos ||
11137 std::atol(tok.c_str()) < 1)
11139 std::string(orders ?
"--busyperiod takes a comma-separated list of "
11141 :
"--busyperiod-subnet takes a comma-separated list of "
11142 "1-based station indexes") +
11143 " (got '" + v +
"')");
11144 into.push_back(
static_cast<std::size_t
>(std::atol(tok.c_str())));
11145 if (comma == std::string::npos)
break;
11148 }
else if (a ==
"--tol") o.knobs.tol = std::atof(next(
"--tol").c_str());
11149 else if (a ==
"--iter_tol") o.knobs.iter_tol = std::atof(next(
"--iter_tol").c_str());
11150 else if (a ==
"--iter_max") o.knobs.iter_max = std::atoi(next(
"--iter_max").c_str());
11151 else if (a ==
"--max-states") {
11152 const std::string v = next(
"--max-states");
11153 const long long n = std::atoll(v.c_str());
11156 "--max-states truncates an open agent's queue-length dimension and takes a "
11157 "positive state count (got '" + v +
"')");
11158 o.knobs.max_states = n;
11160 else if (a ==
"--multiserver") o.knobs.multiserver = next(
"--multiserver");
11161 else if (a ==
"--fork-join" || a ==
"--fork_join")
11162 o.knobs.fork_join = next(
"--fork-join");
11163 else if (a ==
"--tran-points" || a ==
"--tran_points") {
11164 const std::string v = next(
"--tran-points");
11165 const long n = std::atol(v.c_str());
11168 "--tran-points is the number of points on the transient grid and needs at "
11169 "least two, a start and an end (got '" + v +
"')");
11170 o.knobs.tran_points =
static_cast<std::size_t
>(n);
11172 else if (a ==
"--mdd-tol" || a ==
"--mdd_tol") {
11173 const std::string v = next(
"--mdd-tol");
11174 const double d = std::atof(v.c_str());
11176 throw line::InputError(
"--mdd-tol must be a positive tolerance (got '" + v +
"')");
11177 o.knobs.mdd_tol = d;
11178 }
else if (a ==
"--mdd-maxiter" || a ==
"--mdd_maxiter") {
11179 const std::string v = next(
"--mdd-maxiter");
11180 const long n = std::atol(v.c_str());
11183 "--mdd-maxiter must be a positive sweep count (got '" + v +
"')");
11184 o.knobs.mdd_maxiter =
static_cast<int>(n);
11186 else if (a ==
"--fj-accuracy") {
11187 const std::string v = next(
"--fj-accuracy");
11188 const long n = std::atol(v.c_str());
11191 "--fj-accuracy is the FJ_codes truncation C of the queue-length difference "
11192 "between the two fork-join branches and must be at least 1 (got '" + v +
"')");
11193 o.knobs.fj_accuracy =
static_cast<int>(n);
11194 }
else if (a ==
"--fj-tmode") {
11195 const std::string v = next(
"--fj-tmode");
11196 if (v !=
"NARE" && v !=
"Sylves")
11198 "--fj-tmode selects how computeT.m solves for the T matrix and is 'NARE' (the "
11199 "Riccati route, the default) or 'Sylves' (the fixed-point iteration); got '" +
11201 o.knobs.fj_tmode = v;
11202 }
else if (a ==
"--timescale") {
11203 const std::string v = next(
"--timescale");
11204 if (v !=
"auto" && v !=
"discrete" && v !=
"continuous")
11206 "--timescale decides whether the model is read on a slot lattice and is "
11207 "'auto' (the default), 'discrete' or 'continuous'; got '" + v +
"'");
11208 o.knobs.timescale = v;
11210 else if (a ==
"--force") {
11211 o.knobs.force =
true;
11213 else if (a ==
"--cutoff") {
11214 const std::string v = next(
"--cutoff");
11215 if (v.find(
',') != std::string::npos || v.find(
';') != std::string::npos) {
11216 o.knobs.cutoff_mat = parse_cutoff_matrix(v);
11217 if (o.knobs.cutoff_mat.empty())
11219 "--cutoff takes a number or a per-(station,class) matrix written "
11220 "'r1c1,r1c2;r2c1,r2c2' (got '" + v +
"')");
11222 const double d = std::atof(v.c_str());
11225 "--cutoff must be a positive job count per open class (got '" + v +
"')");
11226 o.knobs.cutoff = d;
11228 }
else if (a ==
"--tspan" || a ==
"--timespan") {
11229 const std::string v = next(
"--tspan");
11235 std::string::size_type sep = v.find(
':');
11236 if (sep == std::string::npos) sep = v.find(
',');
11239 const double lo = sep == std::string::npos ? 0.0 : std::atof(v.substr(0, sep).c_str());
11240 const double hi = std::atof(
11241 (sep == std::string::npos ? v : v.substr(sep + 1)).c_str());
11244 if (!(hi > lo) || !(lo >= 0.0) || !std::isfinite(hi))
11246 "--tspan must be a finite horizon 0 <= t0 < t1, given as <t1>, <t0>:<t1> or "
11247 "<t0>,<t1> (got '" + v +
"')");
11250 }
else if (a ==
"-n" || a ==
"--node") {
11251 const std::string v = next(
"--node");
11252 const long n = std::atol(v.c_str());
11254 throw line::InputError(
"--node must be a positive 1-based node index (got '" + v +
11256 o.knobs.node =
static_cast<std::size_t
>(n);
11257 }
else if (a ==
"-c" || a ==
"--class") {
11258 const std::string v = next(
"--class");
11259 const long c = std::atol(v.c_str());
11261 throw line::InputError(
"--class must be a positive 1-based class index (got '" + v +
11263 o.knobs.jobclass =
static_cast<std::size_t
>(c);
11264 }
else if (a ==
"--marg-states" || a ==
"--marg_states") {
11266 const std::string v = next(
"--marg-states");
11267 std::size_t at = 0;
11268 while (at <= v.size()) {
11269 const std::size_t comma = v.find(
',', at);
11270 const std::string tok =
11271 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11272 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
11274 "--marg-states takes a comma-separated list of non-negative job counts "
11275 "(got '" + v +
"')");
11276 o.knobs.marg_states.push_back(std::atol(tok.c_str()));
11277 if (comma == std::string::npos)
break;
11280 }
else if (a ==
"--state") {
11284 const std::string v = next(
"--state");
11285 std::size_t at = 0;
11286 while (at <= v.size()) {
11287 const std::size_t comma = v.find(
',', at);
11288 const std::string tok =
11289 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11290 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
11292 "--state is the state vector -a prob asks about and takes a "
11293 "comma-separated list of non-negative counts (got '" + v +
"')");
11294 o.knobs.state.push_back(std::atol(tok.c_str()));
11295 if (comma == std::string::npos)
break;
11298 }
else if (a ==
"--events") {
11299 const std::string v = next(
"--events");
11300 const double d = std::atof(v.c_str());
11303 "--events is the length of a sampled trajectory and must be a positive event "
11304 "count (got '" + v +
"')");
11305 o.knobs.events =
static_cast<std::size_t
>(d);
11306 }
else if (a ==
"--timestep") {
11307 const std::string v = next(
"--timestep");
11308 const double d = std::atof(v.c_str());
11309 if (!(d > 0.0) || !std::isfinite(d))
11311 "--timestep is the fixed output step of a transient analysis and must be a "
11312 "positive finite time (got '" + v +
"')");
11313 o.knobs.timestep = d;
11314 }
else if (a ==
"--percentiles") {
11315 const std::string v = next(
"--percentiles");
11316 std::size_t at = 0;
11317 while (at <= v.size()) {
11318 const std::size_t comma = v.find(
',', at);
11319 const std::string tok =
11320 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11323 "--percentiles takes a comma-separated list of levels (got '" + v +
"')");
11324 double p = std::atof(tok.c_str());
11331 if (p > 1.0) p /= 100.0;
11332 if (!(p > 0.0) || !(p < 1.0))
11334 "--percentiles levels lie strictly inside (0,1) as fractions or (0,100) "
11335 "as percents; the 100th percentile of an unbounded law is not finite "
11336 "(got '" + tok +
"')");
11337 o.knobs.percentiles.push_back(p);
11338 if (comma == std::string::npos)
break;
11341 }
else if (a ==
"--reward-name" || a ==
"--reward_name") {
11342 o.knobs.reward_name = next(
"--reward-name");
11343 }
else if (a ==
"--notation") {
11344 const std::string v = next(
"--notation");
11347 o.knobs.notation = v;
11348 }
else if (a ==
"--symbolic") {
11352 o.knobs.symbolic = next(
"--symbolic");
11353 }
else if (a ==
"--equilibria") o.knobs.equilibria =
true;
11354 else if (a ==
"--perm-engine") {
11357 o.knobs.method_perm = next(
"--perm-engine");
11358 }
else if (a ==
"--transient-method") {
11361 o.knobs.transient_method = next(
"--transient-method");
11362 }
else if (a ==
"--rate-sched") {
11363 o.knobs.rate_sched = next(
"--rate-sched");
11364 }
else if (a ==
"--ctmc-tv-ngrid") {
11365 const std::string v = next(
"--ctmc-tv-ngrid");
11366 const long n = std::atol(v.c_str());
11368 throw line::InputError(
"--ctmc-tv-ngrid is the grid size of the rate_sched "
11369 "propagator and must be an integer >= 2 (got '" + v +
"')");
11370 o.knobs.ctmc_tv_ngrid =
static_cast<std::size_t
>(n);
11371 }
else if (a ==
"--fau-epsilon") {
11372 const std::string v = next(
"--fau-epsilon");
11373 const double d = std::atof(v.c_str());
11374 if (!(d > 0.0) || !std::isfinite(d))
11376 "--fau-epsilon is the probability mass the transient grid may discard and "
11377 "must be a positive finite number (got '" + v +
"')");
11378 o.knobs.fau_epsilon = d;
11379 }
else if (a ==
"--fau-delta") {
11380 const std::string v = next(
"--fau-delta");
11381 const double d = std::atof(v.c_str());
11382 if (!(d >= 0.0) || !std::isfinite(d))
11384 "--fau-delta is the occupancy below which a state is dropped and must be a "
11385 "nonnegative finite number (got '" + v +
"')");
11386 o.knobs.fau_delta = d;
11387 }
else if (a ==
"--cdf-algorithm") {
11390 o.knobs.cdf_algorithm = next(
"--cdf-algorithm");
11391 }
else if (a ==
"--passage-from") o.knobs.passage_from = next(
"--passage-from");
11392 else if (a ==
"--passage-into") o.knobs.passage_into = next(
"--passage-into");
11393 else if (a ==
"--passage-method") o.knobs.passage_method = next(
"--passage-method");
11394 else if (a ==
"--passage-orders")
11395 o.knobs.passage_orders =
static_cast<std::size_t
>(std::stoul(next(
"--passage-orders")));
11396 else if (a ==
"--no-interlocking") o.knobs.no_interlocking =
true;
11397 else if (a ==
"--interlock-method") o.knobs.interlock_method = next(
"--interlock-method");
11398 else if (a ==
"--interlock-maxpaths")
11399 o.knobs.interlock_maxpaths = std::stod(next(
"--interlock-maxpaths"));
11400 else if (a ==
"--interlock-refpath-scope")
11401 o.knobs.interlock_refpath_scope = next(
"--interlock-refpath-scope");
11402 else if (a ==
"--layer-solver") o.knobs.layer_solver = next(
"--layer-solver");
11403 else if (a ==
"--stage-solver") o.knobs.stage_solver = next(
"--stage-solver");
11404 else if (a ==
"--map-env") o.knobs.map_env = next(
"--map-env");
11405 else if (a ==
"--map-env-method") o.knobs.map_env_method = next(
"--map-env-method");
11406 else if (a ==
"--map-env-maxstages")
11407 o.knobs.map_env_maxstages =
static_cast<std::size_t
>(std::stoul(next(
"--map-env-maxstages")));
11408 else if (a ==
"--ln-transient") o.knobs.ln_transient = next(
"--ln-transient");
11409 else if (a ==
"--ln-transient-channels")
11410 o.knobs.ln_transient_channels = next(
"--ln-transient-channels");
11411 else if (a ==
"--sens-method") o.knobs.sens_method = next(
"--sens-method");
11412 else if (a ==
"--sens-scheme") o.knobs.sens_scheme = next(
"--sens-scheme");
11413 else if (a ==
"--sens-step") {
11414 const std::string v = next(
"--sens-step");
11415 const double h = std::atof(v.c_str());
11416 if (!(h > 0.0) || !(h < 1.0))
11418 "--sens-step is the RELATIVE rate perturbation and must lie in (0,1) (got '" +
11420 o.knobs.sens_step = h;
11422 else if (a ==
"--uq-solver") o.knobs.uq_solver = next(
"--uq-solver");
11423 else if (a ==
"--keep") o.knobs.keep =
true;
11424 else if (a ==
"--verbose") o.knobs.verbose =
true;
11425 else if (a ==
"--remote") o.knobs.remote =
true;
11426 else if (a ==
"--remote-url") {
11429 o.knobs.remote_url = next(
"--remote-url");
11430 o.knobs.remote =
true;
11432 else if (a ==
"--timeout") {
11433 const std::string v = next(
"--timeout");
11434 const long s = std::atol(v.c_str());
11436 throw line::InputError(
"--timeout is a deadline in seconds and must be positive "
11437 "(got '" + v +
"')");
11438 o.knobs.timeout_seconds =
static_cast<int>(s);
11440 else if (a ==
"--repeat") {
11441 const std::string v = next(
"--repeat");
11442 const long n = std::atol(v.c_str());
11444 throw line::InputError(
"--repeat must be a positive run count (got '" + v +
"')");
11445 o.knobs.repeat =
static_cast<int>(n);
11448 else if (a ==
"--ldes-tranfilter") {
11449 const std::string v = next(
"--ldes-tranfilter");
11450 if (v !=
"mser5" && v !=
"fixed" && v !=
"none")
11452 "--ldes-tranfilter selects the warmup filter and is mser5, fixed or none (got '" +
11454 o.knobs.ldes_tranfilter = v;
11456 else if (a ==
"--ldes-warmupfrac") {
11457 const std::string v = next(
"--ldes-warmupfrac");
11458 const double d = std::atof(v.c_str());
11459 if (!(d >= 0.0 && d < 1.0))
11461 "--ldes-warmupfrac is the fraction of the run the fixed filter discards and "
11462 "lies in [0,1) (got '" + v +
"')");
11463 o.knobs.ldes_warmupfrac = d;
11465 else if (a ==
"--ldes-cimethod") {
11466 const std::string v = next(
"--ldes-cimethod");
11467 if (v !=
"obm" && v !=
"bm" && v !=
"spectral" && v !=
"none")
11469 "--ldes-cimethod selects the confidence-interval estimator and is obm, bm, "
11470 "spectral or none (got '" + v +
"')");
11471 o.knobs.ldes_cimethod = v;
11473 else if (a ==
"--ldes-cnvgon") o.knobs.ldes_cnvgon =
true;
11474 else if (a ==
"--ldes-cnvgtol") {
11477 const std::string v = next(
"--ldes-cnvgtol");
11478 const double d = std::atof(v.c_str());
11479 if (!(d > 0.0 && d < 1.0))
11481 "--ldes-cnvgtol is a RELATIVE precision target and lies in (0,1) (got '" + v +
11483 o.knobs.ldes_cnvgtol = d;
11484 o.knobs.ldes_cnvgon =
true;
11486 else if (a ==
"--ldes-slotted") o.knobs.ldes_slotted =
true;
11487 else if (a ==
"--slotted") o.knobs.slotted =
true;
11488 else if (a ==
"--slotlength") {
11490 const std::string v = next(
"--slotlength");
11491 const double d = std::atof(v.c_str());
11494 "--slotlength is the slot of the discrete time scale and must be positive "
11495 "(got '" + v +
"')");
11496 o.knobs.slotlength = d;
11497 o.knobs.slotted =
true;
11499 else if (a ==
"--ldes-slotlength") {
11501 const std::string v = next(
"--ldes-slotlength");
11502 const double d = std::atof(v.c_str());
11505 "--ldes-slotlength is the slot of the discrete time scale and must be positive "
11506 "(got '" + v +
"')");
11507 o.knobs.ldes_slotlength = d;
11508 o.knobs.ldes_slotted =
true;
11510 else if (a ==
"--jmt-replications") {
11511 const std::string v = next(
"--jmt-replications");
11512 const long n = std::atol(v.c_str());
11515 "--jmt-replications is a positive count of independent runs (got '" + v +
"')");
11516 o.knobs.jmt_replications =
static_cast<int>(n);
11518 else if (a ==
"--ldes-replications") {
11519 const std::string v = next(
"--ldes-replications");
11520 const long n = std::atol(v.c_str());
11523 "--ldes-replications is a positive count of independent runs (got '" + v +
"')");
11524 o.knobs.ldes_replications =
static_cast<int>(n);
11526 else if (a ==
"--ldes-numthreads") {
11527 const std::string v = next(
"--ldes-numthreads");
11528 const long n = std::atol(v.c_str());
11531 "--ldes-numthreads is a positive worker count (got '" + v +
"')");
11532 o.knobs.ldes_numthreads =
static_cast<int>(n);
11534 else if (a ==
"--ldes-maxtime") {
11535 const std::string v = next(
"--ldes-maxtime");
11536 const double d = std::atof(v.c_str());
11539 "--ldes-maxtime is a wall-clock budget in seconds and must be positive (got '" +
11541 o.knobs.ldes_maxtime = d;
11543 else if (a ==
"--ldes-initsol") {
11546 const std::string v = next(
"--ldes-initsol");
11548 while (b <= v.size()) {
11549 const std::size_t e = v.find(
',', b);
11550 const std::string tok =
11551 v.substr(b, e == std::string::npos ? std::string::npos : e - b);
11554 "--ldes-initsol is a comma-separated placement with no empty entry (got '" +
11556 o.knobs.ldes_initsol.push_back(std::atof(tok.c_str()));
11557 if (e == std::string::npos)
break;
11561 else if (a ==
"--ldes-rest-url") o.knobs.ldes_rest_url = next(
"--ldes-rest-url");
11562 else if (a ==
"-v" || a ==
"--verbosity") {
11563 const std::string v = next(a.c_str());
11568 if (v !=
"silent" && v !=
"standard" && v !=
"normal" && v !=
"debug" &&
11571 "-v takes silent, standard (the JAR spells it normal) or debug; got '" + v +
11573 o.knobs.verbosity = (v ==
"normal") ?
"standard" : v;
11575 else if (!a.empty() && a[0] ==
'-')
11611 if (o.file.empty() && !g_stdin_loaded)
11612 throw line::InputError(
"--find-solver reports on a model; name one with -f");
11613 const bool layered = o.file.empty()
11616 if (!o.input_given && layered)
11618 "--find-solver reports on a flat Network model; a layered one is solved by -s ln "
11619 "and -s lqns, which this port reaches through the -i lqnx path");
11620 if (o.output !=
"readable" && o.output !=
"json")
11622 "'; --find-solver reports as: readable, json");
11625 net.
get_struct(), o.find_solver_metric, o.find_solver_all);
11626 if (o.output !=
"json") {
11637 for (std::size_t i = 0; i < rows.size(); ++i) {
11639 c[
"solver"] = rows[i].solver;
11640 c[
"method"] = rows[i].method;
11641 c[
"runnable"] = rows[i].runnable;
11642 c[
"methodClass"] = rows[i].method_class;
11643 c[
"reason"] = rows[i].reason;
11645 for (std::size_t k = 0; k < rows[i].metrics.size(); ++k) ms.push_back(rows[i].metrics[k]);
11647 cands.push_back(c);
11650 body[
"type"] =
"FindSolver";
11651 body[
"candidates"] = cands;
11652 j[
"findSolver"] = body;
11653 emit_document(dump_document(j));
11661 if (!o.api.empty()) {
11662 if (o.output !=
"readable" && o.output !=
"json")
11664 "'; accepted forms are: readable, json");
11667 if (o.output ==
"json")
11668 emit_document(dump_document(result, 2));
11676 if (!o.input_given && has_lqn_extension(o.file)) o.input =
"lqnx";
11678 if (!o.input_given && has_jsim_extension(o.file)) o.input =
"jsimg";
11680 if (!o.input_given && has_pnml_extension(o.file)) o.input =
"pnml";
11693 const bool sniffable = o.input !=
"lqnx" && o.input !=
"xml" && o.input !=
"pnml" &&
11694 o.input.compare(0, 4,
"jsim") != 0;
11695 if (sniffable && !o.file.empty() && !has_pnml_extension(o.file) &&
11698 else if (sniffable && o.file.empty() && g_stdin_loaded &&
11701 if (o.input ==
"lqnx" || o.input ==
"xml") {
11702 if (o.output !=
"readable" && o.output !=
"json" && o.output !=
"layers")
11704 "'; accepted forms on the layered path are: readable, "
11712 const std::vector<std::string> as = analysis_list(o.analysis);
11713 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
11714 const int rc = solve_lqn_dispatch(o.arith, o.solver, as[i], o.output, o.file,
11716 if (rc != 0)
return rc;
11719 return solve_lqn_dispatch(o.arith, o.solver, as.back(), o.output, o.file, o.knobs);
11726 if (o.input ==
"jsim" || o.input ==
"jsimg" || o.input ==
"jsimw")
11727 g_jsim_input =
true;
11728 else if (o.input ==
"pnml")
11729 g_pnml_input =
true;
11730 else if (o.input !=
"json")
11732 "the model-solving path reads -i json for a Network model, -i jsim|jsimg|jsimw "
11733 "for a JMT simulation document, -i pnml for a place/transition net and "
11734 "-i lqnx|xml for a layered one (got '" + o.input +
"')");
11757 if (o.output ==
"jsim" || o.output ==
"jsimg" || o.output ==
"jsimw") {
11766 if (o.knobs.seed) wopt.
seed =
static_cast<long>(o.knobs.seed);
11767 if (o.knobs.samples) wopt.
max_samples =
static_cast<double>(o.knobs.samples);
11772 if (o.output !=
"readable" && o.output !=
"json")
11774 "'; accepted forms are: readable, json, jsimg");
11775 g_json_output = (o.output ==
"json");
11778 const std::vector<std::string> as = analysis_list(o.analysis);
11779 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
11780 const int rc = solve_model_dispatch(o.arith, o.solver, as[i], o.file, o.knobs);
11781 if (rc != 0)
return rc;
11784 return solve_model_dispatch(o.arith, o.solver, as.back(), o.file, o.knobs);
11804 if (fd_ < 0)
return;
11805 const char* tmpdir = std::getenv(
"TMPDIR");
11807 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-cap-XXXXXX";
11808 std::vector<char> buf(path.begin(), path.end());
11809 buf.push_back(
'\0');
11810 tfd_ = ::mkstemp(&buf[0]);
11811 if (tfd_ < 0)
return;
11812 path_.assign(&buf[0]);
11813 std::fflush(fd_ == 1 ? stdout : stderr);
11814 saved_ = ::dup(fd_);
11825 if (!path_.empty()) {
11826 std::remove(path_.c_str());
11834 if (path_.empty())
return std::string();
11835 std::ifstream in(path_.c_str());
11836 std::string out((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
11838 std::remove(path_.c_str());
11847 if (saved_ < 0)
return;
11848 std::fflush(fd_ == 1 ? stdout : stderr);
11849 ::dup2(saved_, fd_);
11881 reset_invocation_state(
false);
11885 return "line-cli: cannot create a capture file for the response\n";
11892 err = std::string(
"line-cli: ") + e.what() +
"\n";
11893 }
catch (
const std::exception& e) {
11895 err = std::string(
"line-cli: unexpected failure: ") + e.what() +
"\n";
11897 std::string out = cap.
take();
11900 return err.empty() ? out : out + err;
11916 std::printf(
"--------------------------------------------------------------------\n");
11917 std::printf(
"LINE Solver - Command Line Interface (C++)\n");
11918 std::printf(
"Copyright (c) 2012-2026, QORE Lab, Imperial College London\n");
11919 std::printf(
"Version %s. All rights reserved.\n", kVersion);
11920 std::printf(
"--------------------------------------------------------------------\n");
11921 std::printf(
"Running in server mode on port %d.\n", base.port);
11923 std::printf(
"Quitting after %d request(s).\n", base.maxreq);
11924 std::fflush(stdout);
11927 while (base.maxreq == 0 || served < base.maxreq) {
11928 const bool ok = server.
serve_one([&](
const std::string& msg) -> std::string {
11929 const std::string::size_type nl = msg.find(
'\n');
11930 if (nl == std::string::npos)
11931 return "line-cli: the request's first line is the argument list and its "
11932 "remainder is the model document; this message has no newline\n";
11933 const std::string argline = msg.substr(0, nl);
11934 const std::string model = msg.substr(nl + 1);
11936 const char* tmpdir = std::getenv(
"TMPDIR");
11938 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-req-XXXXXX";
11939 std::vector<char> nb(path.begin(), path.end());
11940 nb.push_back(
'\0');
11941 const int mfd = ::mkstemp(&nb[0]);
11942 if (mfd < 0)
return "line-cli: cannot stage the client model\n";
11944 path.assign(&nb[0]);
11946 std::ofstream mf(path.c_str());
11955 std::vector<std::string> toks;
11956 std::string::size_type at = 0;
11957 while (at <= argline.size()) {
11958 const std::string::size_type comma = argline.find(
',', at);
11959 toks.push_back(argline.substr(
11960 at, comma == std::string::npos ? std::string::npos : comma - at));
11961 if (comma == std::string::npos)
break;
11964 std::string result;
11965 if (toks.size() < 2) {
11966 result =
"line-cli: the argument list needs at least two tokens; the first two "
11967 "are replaced by --file and the staged model path\n";
11969 toks[0] =
"--file";
11971 std::vector<char*> argv;
11972 std::vector<std::string> store;
11973 store.push_back(
"line-cli");
11974 for (std::size_t i = 0; i < toks.size(); ++i) store.push_back(toks[i]);
11975 for (std::size_t i = 0; i < store.size(); ++i)
11976 argv.push_back(
const_cast<char*
>(store[i].c_str()));
11979 Options ro = parse_args(
static_cast<int>(argv.size()), &argv[0]);
11987 result = std::string(
"line-cli: ") + e.what() +
"\n";
11990 std::remove(path.c_str());
12019 if (!spec.is_object())
12021 "{\"kind\":\"network\",\"queues\":3,\"closedClasses\":1}");
12022 const std::string kind = gen_str(spec,
"kind",
"network");
12023 if (kind ==
"network") {
12024 static const char*
const keys[] = {
12025 "kind",
"seed",
"name",
"queues",
"delays",
"openClasses",
"closedClasses",
12026 "schedStrat",
"routingStrat",
"distribution",
"cclassJobLoad",
"varyingServiceRates",
12027 "multiServerQueues",
"randomCSNodes",
"multiChainCS",
"topology"};
12028 gen_check_keys(spec, keys,
sizeof(keys) /
sizeof(keys[0]));
12030 if (spec.contains(
"seed")) g.
set_seed(gen_int(spec,
"seed", 0));
12040 const std::string topo = gen_str(spec,
"topology",
"rand");
12041 if (topo ==
"rand")
12043 else if (topo ==
"cyclic")
12047 "'; accepted forms are: rand, cyclic");
12050 const long nq = gen_int(spec,
"queues", 1);
12051 const long nd = gen_int(spec,
"delays", -1);
12052 const long no = gen_int(spec,
"openClasses", 0);
12053 const long nc = gen_int(spec,
"closedClasses", 1);
12055 static_cast<int>(no),
static_cast<int>(
nc));
12059 if (kind ==
"layered") {
12060 static const char*
const keys[] = {
12061 "kind",
"seed",
"name",
"clients",
"levels",
"tasks",
"processors",
"populationRange",
12062 "thinkTimeRange",
"taskMultiRange",
"procMultiRange",
"hostDemandRange",
12063 "synchCallRange",
"taskInfProbability",
"procInfProbability"};
12064 gen_check_keys(spec, keys,
sizeof(keys) /
sizeof(keys[0]));
12066 if (spec.contains(
"seed")) g.
set_seed(gen_int(spec,
"seed", 0));
12076 const long ncl = gen_int(spec,
"clients", 1);
12077 const long nlv = gen_int(spec,
"levels", 1);
12078 const long ntk = gen_int(spec,
"tasks", 1);
12079 const long npr = gen_int(spec,
"processors", 1);
12081 g.
generate(
static_cast<int>(ncl),
static_cast<int>(nlv),
static_cast<int>(ntk),
12082 static_cast<int>(npr));
12090 "'; accepted forms are: network, layered");
12104int dispatch(
const Options& in,
bool allow_server,
bool no_args) {
12109 if (!o.knobs.verbosity.empty()) g_verbosity = o.knobs.verbosity;
12116 : (g_verbosity ==
"debug" || g_verbosity ==
"verbose")
12123 if (o.help || no_args) {
12124 print_brief_help();
12128 std::printf(
"line-cli %s\n", kVersion);
12151 "-p runs the solver as a server, which this caller did not permit");
12155 if (!o.file.empty())
12157 "-p runs the solver as a server, where each request carries its own model; "
12158 "-f names a model on the command line and the two cannot both be the source");
12170 reset_invocation_state(
false);
12175 std::vector<std::string> docs;
12182 std::vector<std::string> store;
12183 store.push_back(
"line-cli");
12184 for (std::size_t i = 0; i < req.
argv.size(); ++i) store.push_back(req.
argv[i]);
12185 std::vector<char*> argv;
12186 for (std::size_t i = 0; i < store.size(); ++i) argv.push_back(&store[i][0]);
12200 }
catch (
const InterruptRequested& e) {
12202 res.
error = e.what();
12206 res.
error = e.what();
12208 }
catch (
const std::exception& e) {
12210 res.
error = std::string(
"unexpected failure: ") + e.what();
12217 g_json_sink =
nullptr;
12218 g_interrupt_cb =
nullptr;
12219 g_interrupt_user =
nullptr;
12221 reset_invocation_state(
false);
12227 return dispatch(parse_args(argc, argv),
true, argc == 1);
12229 std::fprintf(stderr,
"line-cli: %s\n", e.what());
12231 }
catch (
const std::exception& e) {
12232 std::fprintf(stderr,
"line-cli: unexpected failure: %s\n", e.what());
The -s ag entry point: gates, fixed point, mean measures.
Direct invocation of a single API function from named JSON arguments.
SolverAUTO.listValidMethods: the method names THIS MODEL can actually run.
Redirect a file descriptor into a temporary file for a scope, and give back what was written to it.
std::string take()
Restore the descriptor and read back what was written.
Base error for the multiprecision C++ port.
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
A random lqn::LqnBuilder<T> source, configured once and then drawn from.
void set_host_demand_range(const Range &r)
Activity host demands.
lqn::LqnBuilder< T > generate(int num_clients, int num_levels, int num_tasks, int num_processors)
void set_proc_inf_probability(double p)
Probability that a processor is infinite-server rather than PS.
void set_synch_call_range(const Range &r)
Mean number of synchronous calls on a call arc.
void set_model_name(const std::string &nm)
The name of the generated model.
void set_population_range(const Range &r)
Reference-task populations.
void set_think_time_range(const Range &r)
Client think times.
void set_proc_multi_range(const Range &r)
Processor multiplicities, for the processors that are not infinite-server.
const std::string & model_name() const
void set_task_multi_range(const Range &r)
Task multiplicities, for the tasks that are not infinite-server.
void set_task_inf_probability(double p)
Probability that a server task is infinite-server rather than FCFS.
void set_seed(long long seed)
Reseed the single stream every draw comes from.
A random qn::Network<T> source, configured once and then drawn from.
void set_cclass_job_load(const std::string &load)
high, medium, low, or randomize: the population band of a closed class.
void set_model_name(const std::string &nm)
The name given to the generated model.
void set_seed(long long seed)
Reseed the single stream every draw comes from.
void set_topology(TopologyKind kind)
Pick one of the two shipped topology generators.
void set_distribution(const std::string &d)
Exp, Erlang, HyperExp, or randomize.
void set_multi_chain_cs(bool v)
Classes are partitioned into random chains and switching is confined to a chain, instead of the defau...
void set_sched_strat(const std::string &strat)
fcfs, ps, inf, lcfs, lcfspr, siro, sjf, ljf, sept, lept, or randomize.
void set_random_cs_nodes(bool v)
Each link gets a ClassSwitch node inserted on a coin flip.
qn::Network< T > generate(int num_queues, int num_delays, int num_oclass, int num_cclass)
The full form.
void set_routing_strat(const std::string &strat)
Probabilities, Random, or randomize.
void set_varying_service_rates(bool v)
Service means spread over 2^-6 .
void set_multi_server_queues(bool v)
Queues get 1..40 servers instead of exactly one.
LnSensTable< T > get_sensitivity_table(const sens::SensOptions &sopt)
Port of @SolverLN/getSensitivityTable: solve the ensemble, then concatenate each layer solver's own t...
LnTranSolution get_tran_avg()
Port of @SolverLN/getTranAvg: the block-diagonal aggregate transient.
std::size_t nlayers() const
std::vector< LnCdf > get_cdf_respt()
Port of @SolverLN/getCdfRespT: the per-entry response-time distribution.
LnSolution< T > get_ensemble_avg()
Port of getEnsembleAvg: run the iteration and aggregate onto LQN elements.
const std::vector< qn::Layer< T > > & layers() const
LqnStruct< T > build() const
Flatten into the struct SolverLN consumes.
const LqnModel< T > & model() const
The layered model solved by lqns or lqsim.
static bool is_stochastic_method(const std::string &method)
Only the lqsim methods draw random numbers.
static std::string version()
The version banner of the local binary, empty when there is none.
A layer network: everything a NetworkStruct holds, plus the LQN back-mapping.
A network plus its refreshed NetworkStruct.
std::size_t nvars_of(std::size_t ind) const
Total local-variable width of node ind (1-based).
std::size_t stateful_index(std::size_t ind) const
1-based stateful index of node ind, 0 when the node is not stateful.
std::size_t nof_nodes() const
std::map< std::pair< std::size_t, std::size_t >, Matrix< T > > P
P[(r,s)] is an (nnodes x nnodes) block; absent means all zero.
std::size_t phases_of(std::size_t ist, std::size_t r) const
sn.phases(i,r): the order of the process representation.
std::vector< Reward > reward
std::vector< std::size_t > stateful_nodes
1-based node indices, ascending
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::map< std::size_t, CacheParam< T > > nodeparam
Cache parameters by 1-based NODE index; only Cache nodes have an entry.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< NodeDef > nodes
every node, in creation order
std::vector< std::size_t > station_to_node
(nstations) 1-based node index
std::vector< Region > regions
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
NetworkStruct< T > & raw_struct()
The struct WITHOUT refreshing it, for a caller that is still building.
RAII guard: opens a run on construction, closes it on scope exit.
static void reset()
Forget any open run (used after an interrupted solve).
static void set_verbose(VerboseLevel level)
Set the session verbosity.
A listening socket; one connection is served at a time.
bool serve_one(Fn serve)
Accept one connection, complete the handshake, read ONE text message and hand it to serve; send what ...
The CLI's argument vector, reachable in-process.
Docker primitives for the backends that legitimately ship an image.
The SolverENV entry surface: a port of the analyzer selection that @@SolverENV/SolverENV....
Reader for the LINE model.json interchange of an ENVIRONMENT model into an env::Environment<T>.
The exception types the port throws.
Port of @@SolverFLD/getJacobian, all four of its outputs.
The fluid solver's outermost entry point: @@SolverFLD/runAnalyzer.m's method resolution over solver_f...
The log-driven half of SolverJMT: linkAndLog, parseLogs, parseTranState, parseTranRespT,...
Port of @@JMTIO: a refreshed NetworkStruct written out as a JMT .jsimg simulation model.
Read a JMT .jsim / .jsimg / .jsimw model into a qn::Network.
Random layered-queueing-network generation: the C++ twin of MATLAB @LayeredNetworkGenerator,...
The NATIVE LDES engine for LAYERED (LQN) models, the C++ twin of jline/solvers/ldes/handlers/Solver_s...
int dispatch(const Options &in, bool allow_server, bool no_args)
Everything main did, with the arguments already in hand.
int run_invocation(Options o)
#define LINE_CLI_TABLE_LADDER(FN)
int find_solver_report(const Options &o)
Everything one invocation does once the arguments are in hand.
int run_generate(const Options &o)
--generate: draw a random model and print it, solving nothing.
std::string run_invocation_captured(Options o, int &rc)
Run one invocation with stdout captured, and return what it printed.
int run_server(const Options &base)
-p/--port: serve solve requests over a WebSocket, as LineWebSocketServer does.
Running progress log of a LINE solver run (the "solver console").
model.json with type: "LayeredNetwork" -> LqnStruct, via LqnBuilder.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
LqnModel -> .lqnx, a port of matlab/src/lang/layered/@LayeredNetwork/writeXML.m.
The flat-Network path of @@SolverLQNS: its qns methods, served by qnsolver of the RADS/LQNS distribut...
Port of @NetworkSolver/mapEnvApprox.m: the solver-agnostic random-environment approximation of a netw...
The stage solvers map_env_approx injects, one per runner that can be a caller.
Boundary marshalling for host bindings (MATLAB MEX, pybind11, the JSON CLI).
Classification of a solution method, as printed in the solver banner: "<accuracy>,...
mva::AvgResult< T > solver_ag_run_analyzer(const qn::NetworkStruct< T > &L, const AgOptions &opt)
SolverAG.runAnalyzer: the converged agents as mean measures.
Matrix< T > sn_declared_marginal(const qn::NetworkStruct< T > &sn)
The (nstations x nclasses) per-class job counts of the model's OWN state.
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...
int main_body(int argc, char **argv)
The binary's main, so line_cli_main.cpp stays ten lines.
Response run(const Request &req)
Run one invocation and return what it produced.
Matrix< T > solver_ctmc_sample(const NetworkStruct< T > &sn, const CtmcSamplePath< T > &path, std::size_t ind)
Port of @@SolverCTMC/sample: the walk restricted to ONE stateful node's local block.
Matrix< T > solver_ctmc_sample_aggr(const NetworkStruct< T > &sn, const CtmcSamplePath< T > &path, std::size_t ind)
Port of @@SolverCTMC/sampleAggr: one node's per-class counts over time.
std::vector< CdfCurve< T > > solver_ctmc_cdf_sys_respt(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getCdfSysRespT.m: the per-chain SYSTEM response-time CDF, indexed by chain.
T solver_ctmc_jointaggr(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_jointaggr: P(the network holds exactly these per-class counts),...
CtmcTranProb< T > ctmc_get_tran_prob_sys(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr)
Port of @@SolverCTMC/getTranProbSys.m: pi(t), labelled by the whole network state with its phases.
CtmcSamplePath< T > solver_ctmc_sample_sys(const NetworkStruct< T > &sn, const CtmcOptions &opt_in, std::size_t nevents, unsigned long seed=23000)
Port of @@SolverCTMC/sampleSys: a marked walk on the whole network state.
CtmcTranProb< T > ctmc_get_tran_prob_sys_aggr(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr)
Port of @@SolverCTMC/getTranProbSysAggr.m: pi(t), labelled by the network's per-(station,...
mva::AvgResult< T > solver_ctmc_avg_table(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const std::string &method)
Port of @@SolverCTMC/runAnalyzer.m's result assembly: solve, then apply the metric filter @@NetworkSo...
CtmcMddSolution< T > solver_ctmc_mdd_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const mdd::MddMcdOptions &mcdopt=mdd::MddMcdOptions())
Solve with the mdd method.
CtmcGenerator< T > ctmc_get_infgen(const NetworkStruct< T > &sn, const CtmcSolution< T > &d)
@@SolverCTMC/getInfGen.m, a pure alias of getGenerator in the reference.
std::vector< T > solver_ctmc_margaggr(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_margaggr: per STATION, P(that station holds exactly these per-class counts).
std::vector< CtmcSensRank< T > > solver_ctmc_sensitivity_ranking(const NetworkStruct< T > &sn, const CtmcOptions &opt, const std::vector< CtmcSensParam< T > > ¶ms, const std::vector< T > &reward)
Port of @@SolverCTMC/getSensitivityRanking: rank parameters by influence.
std::vector< std::vector< CdfCurve< T > > > solver_ctmc_cdf_respt(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getCdfRespT.m: the per-(station, class) response-time CDF, indexed [ist-1][r-1].
std::vector< T > solver_ctmc_marg(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_marg: per STATION, P(that station is in exactly its local slice of state),...
std::vector< T > solver_ctmc_avg_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, std::vector< std::string > *names=nullptr)
Port of @@SolverCTMC/getAvgReward: the steady-state expected rewards.
CtmcStateSpace< T > ctmc_get_state_space(const NetworkStruct< T > &, const CtmcSolution< T > &d)
Port of @@SolverCTMC/getStateSpace.m.
CtmcTransient< T > solver_ctmc_transient_analyzer(const NetworkStruct< T > &sn_arg, const CtmcOptions &opt, const T &t0, const T &t1, const std::vector< T > &grid=std::vector< T >())
Port of solver_ctmc_transient_analyzer.m.
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...
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.
std::string jmt_write_jsim(const qn::NetworkStruct< T > &sn, const JmtWriteOptions &opt)
Port of @@JMTIO/writeJSIM.m: serialize sn as a JMT .jsimg document.
env::Environment< T > build_environment_from_json(const detail::json &root)
Build an env::Environment<T> from a parsed model.json envelope.
env::Environment< T > read_environment_json(const std::string &path)
Parse a model.json file into an env::Environment<T>.
qn::Network< T > build_network_from_json(const detail::json &root)
Build a qn::Network<T> from a parsed model.json envelope.
bool docker_daemon_available()
True if the Docker daemon is reachable.
detail::json network_json_envelope(const qn::NetworkStruct< T > &sn)
The complete model.json envelope: {format, version, model}.
const char * error_id(const Error &e)
Stable identifier for an error, for mexErrMsgIdAndTxt and for mapping to a host exception class.
bool is_layered_json_text(const std::string &text)
True when a DOCUMENT ALREADY IN MEMORY is a LayeredNetwork model.json.
qn::Network< T > pnml_load(const std::string &path, const std::string &net_id=std::string())
Read one net of a PNML place/transition document into a Network.
JmtReplication< T > jmt_transient_replications(const qn::NetworkStruct< T > &sn, const JmtOptions &opt)
Port of the transient ensemble of @@SolverJMT/runAnalyzer.m (default over a finite timespan).
JmtSysTrace< T > jmt_sample_sys_aggr(const qn::NetworkStruct< T > &sn, std::size_t num_events, const JmtOptions &opt)
Port of sampleSysAggr: every station's trajectory on one time grid.
JmtNodeTrace< T > jmt_sample_aggr(const qn::NetworkStruct< T > &sn, std::size_t node, std::size_t num_events, const JmtOptions &opt)
Port of sampleAggr: the queue-length trajectory of one node.
std::vector< std::string > jmt_list_valid_methods()
Port of SolverJMT.listValidMethods.
std::size_t jmt_transient_replication_count(const JmtOptions &opt, const std::string &what)
How many JSIM runs a transient estimate is over: the gate the two transient arms share.
JmtResult< T > solver_jmt_run_analyzer(const qn::NetworkStruct< T > &sn, const JmtOptions &opt_in)
Port of @@SolverJMT/runAnalyzer.m, the jsim and jmva arms.
std::map< std::pair< std::size_t, std::size_t >, std::vector< std::pair< double, double > > > jmt_get_cdf_resp_t(const qn::NetworkStruct< T > &sn, const JmtOptions &opt, bool seed_from_steady=true)
Port of getCdfRespT: the empirical response-time distribution per (station, class),...
std::string jmt_removed_method_refusal(const std::string &method)
The migration sentence for a method name SolverJMT no longer has; empty for any other name.
std::pair< std::vector< double >, std::vector< std::vector< double > > > jmt_get_tran_prob_aggr(const qn::NetworkStruct< T > &sn, std::size_t station, const JmtOptions &opt, std::vector< std::vector< double > > &states_out)
Port of getTranProbAggr: the transient distribution of one station's aggregate state,...
JmtProbAggr jmt_prob_aggr(const qn::NetworkStruct< T > &sn, const JmtOptions &opt, std::size_t target_station=0, const std::vector< double > &target=std::vector< double >())
Port of getProbAggr and getProbSysAggr, both off ONE instrumented run.
const char * sched_to_text(SchedStrategy s)
const char * event_to_text(EventType e)
LnColumn
A column of the shared layered average table, as line-cli prints it.
bool ln_defined(const lqn::LqnStruct< T > &lsn, const LnResult &r, std::size_t i, LnColumn c)
Does the element at i HAVE the quantity in column c?
std::vector< LnEntryCdf > ldes_ln_cdf_respt(const LnResult &r, std::size_t nentries)
The empirical response time CDF of every ENTRY, the getCdfRespTLN of the other codebases: one [F(t),...
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)
std::string lqnx_to_string(const LqnModel< T > &m, const std::string &model_name=std::string("LQN"), bool use_abstract_names=false)
The .lqnx document as a STRING, for a caller with no file to write to – the CLI's model-generation mo...
mva::AvgResult< T > solve_network_run_analyzer(const qn::NetworkStruct< T > &L, const QnsOptions &opt)
Port of @@SolverLQNS/runAnalyzerNetwork.m and solver_qns.m: SolverLQNS on a flat Network.
const std::string & lqns_version()
The version banner of the local lqns, empty when there is none.
bool lqns_is_available()
True when lqns is installed AND is a release this port speaks.
bool qnsolver_is_available()
Port of SolverLQNS.hasQnsolver: a native qnsolver binary on the PATH.
std::vector< T > mam_percentiles_from_cdf(const RespTCdf< T > &cdf, const std::vector< double > &pcts)
Port of the CDF path of @@SolverMAM/getPerctRespT.m: linear interpolation of the response-time CDF at...
TranResult< T > solver_mam_get_tran_avg(const qn::NetworkStruct< T > &L, const MamOptions &opt_in)
Port of @@SolverMAM/getTranAvg.m: transient queue length, utilization and throughput.
std::vector< RespTCdf< T > > solver_mam_get_cdf_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt)
@@SolverMAM/getCdfRespT.m: the response-time CDF per class.
mva::AvgResult< T > solver_mam_run_analyzer(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of @@SolverMAM/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@N...
ProbTable< T > solver_mam_get_prob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
@@SolverMAM/getProb.m: the joint (level, phase) table at a node.
std::vector< std::vector< T > > solver_mam_get_perct_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m: response-time percentiles per class.
bool mam_has_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Whether getPerctRespT reads the FJ_codes table rather than inverting a CDF.
std::vector< T > solver_mam_get_prob_marg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
@@SolverMAM/getProbMarg.m: P(n jobs of one class) at a station.
qsys::BmapM1Result< T > solver_mam_get_mam_result(const qn::NetworkStruct< T > &L)
@@SolverMAM/getMAMResult.m: the M/G/1-type internals of a single queue.
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
AggrResult< T > solver_mva_get_prob_aggr(const qn::NetworkStruct< T > &L, const AvgResult< T > &avg, std::size_t ist, const std::string &method="default")
T solver_mva_get_prob_norm_const_aggr(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of @@SolverMVA/getProbNormConstAggr.m: log G.
std::string resolve_method(const qn::NetworkStruct< T > &L, const std::string &method)
Port of SolverMVA.resolveMethod: the feature-driven default -> rqna upgrade for a bursty single-class...
MargResult< T > solver_mva_get_prob_marg(const qn::NetworkStruct< T > &L, const AvgResult< T > &avg, std::size_t ist, std::size_t r, const std::vector< long > &states, const std::string &method="default")
Port of @@SolverMVA/getProbMarg.m: P(n jobs of class r at station i) for the states in states (or the...
AggrResult< T > solver_mva_get_prob_sys_aggr(const qn::NetworkStruct< T > &L, const AvgResult< T > &avg, const std::string &method="default")
Port of @@SolverMVA/getProbSysAggr.m: the joint probability of the model's whole state across all sta...
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
AvgResult< T > solver_mva_run_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt_in, const Matrix< T > &init_sol)
Port of @@SolverMVA/runAnalyzer.m for the lang='matlab' path: gate, solve, convert,...
NcCftpSolution< T > solver_nc_cftp(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const NcCftpOptions &cftpopt)
Solve with the cftp / cftp.approx method.
mva::AvgResult< T > solver_nc_run_analyzer(const qn::NetworkStruct< T > &L_in, const NcSolverOptions &opt_in)
T solver_nc_getprob_sys_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const std::vector< int > &nvec, const std::string &engine="exact")
Port of @@SolverNC/getProbSysMarg.m.
T solver_nc_getprob_sys_aggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir)
Port of @@SolverNC/getProbSysAggr.m.
NcSolution< T > solver_nc_cftp_solution(const NcCftpSolution< T > &s)
A cftp solve in the shape every other SolverNC analyzer returns, so the runner's metric filter applie...
T solver_nc_joint(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double *lG_out)
Port of solver_nc_joint.m: the probability of the WHOLE system state.
std::vector< double > solver_nc_busyp(const qn::NetworkStruct< T > &sn, const std::vector< std::size_t > &subnet, const std::vector< std::size_t > &orders)
Mean busy period of order n for a set of stations.
NcQueueLengthDist< T > solver_nc_getprob_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t ist)
Port of @@SolverNC/getProbMarg.m: the TOTAL queue-length distribution.
NcMargResult< T > solver_nc_margaggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double lG)
Port of solver_nc_margaggr.m.
bool is_stochastic_method(const std::string &method)
Port of SolverNC.isStochasticMethod.
NcSolution< T > solver_nc_solve(const qn::NetworkStruct< T > &L_in, const NcSolverOptions &opt_in)
The gates, the multiserver conversion and the dispatch of @@SolverNC/runAnalyzer.m,...
CdfRespTResult< T > solver_nc_cdf_respt(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Port of @@SolverNC/getCdfRespT.m.
NcMargResult< T > solver_nc_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double lG)
Port of solver_nc_marg.m: the DETAILED marginal, which weighs the station's internal arrangement and ...
std::vector< std::vector< int > > MarginalState
A state, as this port expresses it: nir[i][r] jobs of class r at station i.
mva::AvgResult< T > solver_nc_avg_table(const qn::NetworkStruct< T > &L_in, const NcSolution< T > &d, const std::string &origmethod)
Port of @@SolverNC/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@Ne...
std::string solver_nc_cftp_supports(const qn::NetworkStruct< T > &sn)
The cftp model-class gate as a public predicate.
NcldMethod
The load-dependent methods this port dispatches.
std::vector< std::vector< int > > multichoose_rows(int n, int k)
All n-vectors of nonnegative integers summing to k, in MATLAB multichoose(n,k) order.
bool ncld_method_try(const std::string &s, NcldMethod &out)
Map a method name to its enum; false when the name is not one of them.
FeatureSet fluid_feature_set(const std::string &method)
SolverFLD.getFeatureSet, transcribed, MINUS what the requested method cannot evaluate – the port of @...
Marginal< T > to_marginal(const NetworkStruct< T > &sn, std::size_t ist, const std::vector< T > &state_i, const std::vector< std::size_t > &phasesz, const std::vector< std::size_t > &phaseshift, std::size_t nvar=0)
Port of State.toMarginal for a STATION, one state row at a time.
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
FeatureSet mva_feature_set(const std::string &raw_method)
void feature_gate(const std::string &solver, const FeatureSet &declared, const NetworkStruct< T > &sn, const std::string &requested_method="", const std::string &resolved_method="")
runAnalyzerChecks: refuse a model the solver does not declare, by name.
std::string api_render_readable(const Json &result)
Human-readable rendering of the object api_invoke returns, for -o readable.
ArithSpec parse_arith(const std::string &text)
Parse –arith.
Json api_invoke(const std::string &name, const std::string &arith, const Json &args)
Invoke one API function.
SensTable< T > solver_sensitivity_table(qn::NetworkStruct< T > &sn, const SensOptions &opt, bool exact_available, const std::function< mva::MvaSolution< T >()> &solve)
Build the sensitivity table of sn under solve.
void put(Matrix< double > &A, std::size_t r0, std::size_t c0, const Matrix< double > &S)
A(r0:, c0:) = S.
double dot(const std::vector< double > &a, const std::vector< double > &b)
The inner product of a row vector with a column held as a vector.
MapEnvDecision needs_map_env(const qn::FeatureSet &declared, const qn::NetworkStruct< T > &sn, const MapEnvConfig &cfg=MapEnvConfig())
needsMapEnv: does this model need the environment image, and would the image make it solvable?
std::vector< std::string > chain_class_labels(const qn::NetworkStruct< T > &sn)
(ClassA ClassB), the JobClasses column: which classes a chain holds.
std::vector< bool > node_table_hidden_classes(const line::qn::NetworkStruct< T > &sn)
The classes getAvgNodeTable omits: the auxiliary retrieval classes that Cache.setRetrievalSystem mint...
env::EnvStageAvgFn< double > nc_stage_fn(const nc::NcSolverOptions &opt)
NC stages, bound to the caller's own NcSolverOptions.
SysResult< T > solver_get_avg_sys(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgSys.m.
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)
mva::AvgResult< T > run_avg(const qn::NetworkStruct< T > &sn, const std::string &solver, const qn::FeatureSet &declared, const MapEnvConfig &cfg, Run run, StageFn stage_fn, const std::string &requested_method="default")
The getAvg funnel: run the model, or its environment image when the ONLY thing in the way is a non-re...
ChainResult< T > solver_get_avg_node_chain(const qn::NetworkStruct< T > &sn, const Matrix< T > &QNn, const Matrix< T > &UNn, const Matrix< T > &RNn, const Matrix< T > &WNn, const Matrix< T > &ANn, const Matrix< T > &TNn)
Port of @@NetworkSolver/getAvgNodeChain.m: the NODE table aggregated by chain.
ChainResult< T > solver_get_avg_chain(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgChain.m: the station table aggregated by chain.
mva::AvgResult< T > map_env_approx(const qn::NetworkStruct< T > &sn, const std::string &solver, const MapEnvConfig &cfg, StageFn stage_fn, const std::string &requested_method="default")
mapEnvApprox: solve the model through the random-environment image of its non-renewal processes.
std::vector< std::vector< DefaultCdfCurve > > solver_default_cdf_respt(const qn::NetworkStruct< T > &sn, const Matrix< T > &RN)
The NetworkSolver base-class response-time CDF: an exponential law with the right mean per (station,...
std::vector< std::string > chain_names(std::size_t nchains)
Chain1, Chain2, ... – the reference's own chain labels.
env::EnvStageAvgFn< double > fluid_stage_fn(const fluid::FluidOptions &opt)
Fluid stages, bound to the caller's own FluidOptions.
env::EnvStageAvgFn< double > mva_stage_fn(const mva::MvaOptions &opt)
MVA stages, bound to the caller's own MvaOptions.
SsaSerialSolution< T > solver_ssa_serial_analyzer(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt)
Port of solver_ssa_analyzer_serial.m plus the fork-join wrapper @@SolverSSA/runAnalyzer....
SsaSamplePath< T > ssa_sample_node(const qn::NetworkStruct< T > &sn, const SsaSerialRun< T > &r, std::size_t ind)
sample(node) and sampleAggr(node): the same trajectory, one node's block.
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)
@@SolverSSA/runAnalyzer itself: the engine the method selects, then the result assembly the reference...
SsaProbReport solver_ssa_prob(const qn::NetworkStruct< T > &sn_arg, const SsaSerialOptions &opt)
The whole -a prob report over the model's DEFAULT INITIAL STATE, which is the state SolverCTMC's own ...
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.
Conservation laws of a layered queueing network, enumerated from its structure.
const char * arith_name(Arith a)
const std::vector< ApiEntry > & api_registry()
The registry is a function-local static, not a global object, so there is no static-initialization or...
Random queueing-network generation: the C++ twin of MATLAB @NetworkGenerator, the JAR jline....
Reader for the LINE model.json interchange (a Network model) into a qn::Network<T> built through the ...
Number-type abstraction for the templated API port.
PNML (ISO/IEC 15909-2) place/transition nets, read and written.
Coverage registry of the C++ port.
Ports of matlab/src/api/sn/sn_get_node_arvr_from_tput.m and sn_get_node_tput_from_tput....
Ports of matlab/src/api/sn/sn_get_state_aggr.m and sn_is_state_valid.m.
The SolverAUTO chooser: which solver a model is handed to.
The SolverBA class surface: @@SolverBA/runAnalyzer.m, listValidMethods, getBounds and getBoundsTable.
The CHAIN-level and SYSTEM-level views of a solved model.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Port of @@SolverCTMC/getCdfRespT.m and @@SolverCTMC/getCdfSysRespT.m: the exact distribution of the r...
The remaining @@SolverCTMC accessors: getGenerator / getInfGen, getStateSpace / getStateSpaceAggr and...
The mdd method of SolverCTMC: stationary analysis of a closed single-class network whose state space ...
The SolverCTMC probability family: solver_ctmc_joint, _jointaggr, _marg, _margaggr,...
Port of solver_ctmc_reward.m and the @@SolverCTMC reward surface (runRewardAnalyzer,...
Port of the @@SolverCTMC sampling surface: sample, sampleAggr, sampleSys, sampleSysAggr.
Port of @@SolverCTMC/getSensitivity and getSensitivityRanking: the parametric sensitivity of a steady...
Port of solver_ctmc_fcr_waitq.m: the reachability-built generator of a model whose finite capacity re...
The base-class fallback for a response-time CDF.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
Port of SolverJMT, the Java Modelling Tools client.
Port of SolverLDES, the discrete-event simulator, as its C++ client.
SolverLN: layered decomposition of a layered queueing network.
SolverLQNS: the layered model solved by the external lqns / lqsim binaries.
The SolverMAM class surface: @@SolverMAM/runAnalyzer.m and the gates around it.
The state-probability half of the SolverMVA class surface.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
Port of @SolverNC/getAvgBusyPeriod.m and of the Python-native SolverNC.getAvgBusyPeriod: the mean bus...
Port of @@SolverNC/getCdfRespT.m, and of its two aliases getSjrnT and sjrnT.
The cftp and cftp.approx methods of SolverNC: stationary analysis of a closed single-class product-fo...
The state-probability half of the SolverNC class surface: ports of solver_nc_marg....
The SolverNC class surface: @@SolverNC/runAnalyzer.m and the gates around it.
The NODE-indexed view of a station result, behind getAvgNodeTable.
Performance sensitivities with respect to service rates.
The SolverSSA queries that are not the average table: getProb, getProbAggr, getProbSys,...
The SolverSSA entry surface: a port of @@SolverSSA/runAnalyzer.m's method whitelist,...
std::string method
'default', 'inap', 'inaprc', 'inapinf' or the vestigial 'exact'.
int iter_max
SolverOptions('AG') lowers this from the global 1000 to 100.
double tol
Convergence tolerance of the reversed-rate fixed point.
std::size_t max_states
Truncation level of an OPEN agent's queue-length dimension.
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/bjbh/cbh/sib.
std::string method
Bound method; default resolves to gb.upper in the runner.
Matrix< double > qrf_alpha
options.config.qrf_alpha, the (nstations x N) load-dependent scaling of the two load-dependent QRF ar...
One invocation: the argument vector, plus what a pipe would have carried.
bool collect_json
Collect the JSON documents as they are emitted.
int(*) interrupt(void *user)
Polled between analyses and at each document emission; non-zero aborts.
std::vector< std::string > argv
The argument vector WITHOUT argv[0], e.g.
std::string stdin_text
The model document, as if piped to the binary's stdin.
bool capture_stderr
Redirect fd 2 for the duration of the call into Response::diagnostics.
bool capture_stdout
Redirect fd 1 for the duration of the call into Response::text.
bool allow_server
Permit -p, which serves until its request budget is exhausted.
What one invocation produced.
int exit_code
The CLI's own exit status: 0 ok, 2 line::Error, 3 other.
std::string error_id
line::io::error_id's stable identifier for error.
std::vector< std::string > documents
Each JSON document the run emitted, in emission order, AS EMITTED.
std::string error
Empty on success; the what() of the exception otherwise.
std::string text
Everything written to fd 1, with the collected documents still in it.
std::string diagnostics
Everything written to fd 2: warnings, and the failure message.
A CTMC solve routed to whichever path the model's region rules require.
std::vector< T > parked
mean parked jobs per class; empty off the WAITQ path
The answer of @@SolverCTMC/getFirstPassTMoments.m.
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,...
std::vector< CtmcRateSched > rate_sched
options.config.rate_sched: the TIME-INHOMOGENEOUS transient.
std::size_t ctmc_tv_ngrid
options.config.ctmc_tv_ngrid: uniform grid size of the rate_sched propagator.
double fau_epsilon
options.config.fau_epsilon: total probability mass the whole grid may discard under "fau".
bool keep_filtration
Keep the per-synchronization EVENT FILTRATION alongside Q.
One entry of options.config.rate_sched: the rate of (station, class) follows the piecewise-linear sch...
std::vector< double > rates
std::vector< double > tgrid
What the reward analyzer produces, per declared reward.
std::vector< Matrix< T > > V
V[r] is (Tmax+1 x nstates).
std::vector< T > t
iteration index / q
Matrix< T > state_space_aggr
the rows the reward saw
std::vector< std::string > names
One sampled trajectory of the chain.
std::vector< std::size_t > event
synchronization that fired to LEAVE it
std::vector< std::size_t > state
0-based index into chain.space
std::vector< T > t
time at which each state was ENTERED
The scalar parameter a sensitivity is taken with respect to.
std::function< void(NetworkStruct< T > &, double)> set
Apply theta to a COPY of the struct; the original is never mutated.
Everything one CTMC solve produces.
std::string warning
Set when the chain is a reducible mixture solved from an invented seed.
std::vector< std::size_t > cutoff
the per-class cutoff actually used
std::vector< T > pi
stationary distribution over chain.space
[stateSpace, localStateSpace] of @@SolverCTMC/getStateSpace.m.
std::vector< std::size_t > node_width
column width of each stateful node's block
std::vector< Matrix< T > > local
localStateSpace{f}: one matrix per stateful node, its DISTINCT local rows in first-appearance order.
Matrix< T > flat
the blocks concatenated, as MATLAB returns them
The time-dependent answer of one getTranProb* query.
Matrix< T > pit
(ntimes x nstates) occupancy over the solved chain
std::vector< T > t
The reference returns Pi_t = [t, pi_t], one matrix with time glued on as column 1.
Matrix< T > labels
(nstates x width) the state descriptor the query asked for
What one transient CTMC solve produces.
What the ENV entry reports: the environment-blended metrics, and the whole result of whichever coupli...
std::string method
What ran: meanfield, statevec, or the limit avg / dec.
EnvStatevecSolution< T > statevec
Populated on the state-vector path.
bool converged
True on the limit path: a closed form has converged by construction.
solvers::CacheMetrics< T > cache
The environment-blended cache surface, whichever coupling produced it.
int iterations
ZERO ON THE LIMIT PATH, and that is the answer rather than a gap: a limit reads the environment once ...
std::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.
A closed interval the generator samples from.
The simulation controls the JSIM header carries, MATLAB's JMTIO properties.
std::string log_path
model.getLogPath, the logPath attribute
std::string file_name
base name; the header echoes it plus .jsimg
double max_simulated_time
The per-class queue-length trajectory of one node, plus its event stream.
The options of one JMT solve, SolverOptions('JMT') restricted to what is read.
int replications
options.config.replications: the independent JSIM runs the transient ensemble of default averages ove...
std::string method
default | jsim | jmva | jmva.<alg>
bool keep
keep the scratch directory after the solve
double max_simulated_time
double samples
samples per measure; raised to 5000 below
What jmt_prob_aggr reports: the system probability and the per-station ones.
std::vector< bool > station_seen
the same, per station
bool sys_seen
whether the joint state occurred at all
double sys
P(the whole network is in the declared state).
std::vector< double > station
P(station i holds its declared per-class counts).
Transient averages over independent replications, on one time grid.
std::vector< std::vector< std::vector< double > > > UNt
std::size_t valid
Replications that produced a usable trajectory.
std::vector< std::vector< std::vector< double > > > TNt
std::vector< std::vector< std::vector< double > > > QNt
QNt[ist-1][r] over t.
The result of a JMT solve: the shared AvgResult plus what only JMT reports.
std::map< std::size_t, std::vector< T > > cache_hit_prob
Per Cache node (1-based node index), the per-class hit probability.
Matrix< T > ACI
confidence half-widths, empty when disabled
Matrix< T > DropRateNfcr
(nregions x nclasses) carried and lost rate
The system trajectory: one per-class block per station, on a common grid.
static Distrib exp_rate(const T &r)
static constexpr double FineTol
static constexpr double CoarseTol
The knobs of one LDES run.
double warmupfrac
–warmupfrac, only for tranfilter=fixed
bool verbose
echo the resolved command line before running it
std::string rest_url
Base URL of an LDES REST server (the imperialqore/ldes container).
long seed
–seed; -1 requests a random stream
bool slotted
–slotted, run on the slot lattice
std::string cimethod
–cimethod: obm | bm | spectral | none
double slot_length
–slotlength
int replications
–replications; 0 = not given (one path)
std::size_t events
0 = not given; overrides samples when set
std::string tranfilter
–tranfilter: mser5 | fixed | none
std::vector< double > init_sol
–initsol, the warm-start placement as a STATION-MAJOR vector [st0_cl0, st0_cl1, .....
int numthreads
–numthreads; 0 = not given
std::size_t samples
-s, service-completion budget
double timeout
–maxtime, a COOPERATIVE wall-clock budget the event loop polls.
bool has_timespan
true when [t0,t1] was set: a TRANSIENT run
One ldes-result document, parsed.
std::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....
double simulated_time
Length of the MEASURED span, from the end of warmup to the end of the run.
long long completions
Reference-task cycles completed in the measured span.
getSensitivityTable of the ensemble: the layer tables under a Layer column.
std::string method
The summary label: the common branch, or "mixed" when they differ.
The LQN-level answer, indexed by element 1..nidx.
std::vector< bool > defined_W
std::vector< bool > defined_U
std::vector< bool > defined_R
std::vector< bool > defined_Q
bool is_bound
True when the numbers are a BOUND (method = mw.upper / mw.lower) rather than the fixed point.
std::vector< bool > defined_T
The layered transient: one block per layer, plus how it was produced.
The intermediate model, and the second stage that flattens it.
std::vector< LqnElement > type
(nidx+1)
std::vector< bool > iscache
(tshift+ntasks+1)
std::vector< std::string > names
(nidx+1) declared name
std::vector< bool > isref
(tshift+ntasks+1)
Knobs of the wrapper, the subset of SolverOptions that reaches lqns.
The six measures, on the element index space, with a defined mask each.
std::vector< bool > defined_U
std::vector< bool > defined_W
std::vector< bool > defined_Q
std::vector< bool > defined_R
std::vector< bool > defined_T
SolverLQNS.defaultOptions on a flat Network plus the knobs the JMVA document carries.
int timeout
Seconds before a hung qnsolver is killed; not positive waits forever.
bool keep
options.keep: leave the scratch directory behind, to inspect what was sent.
std::string multiserver
options.config.multiserver.
std::string method
default, qns or qns.NAME; see qns_methods().
The options SolverMAM reads.
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
The knobs of one perfect-sampling run.
std::size_t samples
Number of iid stationary draws, SolverOptions('NC').samples by default.
unsigned long seed
Stream seed, so a row is reproducible within this port.
What one cftp solve produces beside the means.
std::vector< long > horizon
(samples) per-draw coalescence horizon, or the mixing steps of M_A.
std::vector< std::vector< int > > distinct_states
The distinct sampled states, aligned with paggr.
What the marginal analyzers return: one probability per station.
std::vector< T > P
(M) probability that station i holds its given vector
The [Q,U,R,T,C,X,lG] of the reference, plus the algorithm that ran.
Controls, defaulting to SolverOptions('NC') in the reference.
double slotlength
options.config.slotlength, the slot length in model time units.
std::string multiserver
options.config.multiserver: how a finite multiserver station is represented.
std::string fork_join
options.config.fork_join: which fork-join arm the shared fixed point takes on a model with a Fork.
std::size_t samples
options.samples, read by the estimators
unsigned long seed
options.seed, read by the estimators
bool slotted
options.config.slotted.
std::string cdf_algorithm
options.config.algorithm for getCdfRespT: 'exact' selects pfqn_stdf, 'rd' the heuristic pfqn_stdf_heu...
double iter_tol
options.iter_tol, the eta stopping test
What State.toMarginal returns for one station and one state row.
std::vector< T > nir
jobs per class
One network state: the per-stateful-node local rows it is composed of.
std::vector< std::vector< T > > local
local[isf] is that node's state row
Everything qsys_bmapm1 returns, mirroring the MATLAB result struct.
T q
uniformization constant actually used
T drift
stable iff strictly negative
T pi0
probability the system is empty
Matrix< T > levelProb
level probabilities, row n = pi_n
T lambda
mean arrival rate, theta (sum_k k D_k) e
std::vector< T > theta
stationary vector of the BMAP phase process
T rho
offered load lambda/mu
double decayRate
measured pi_(n+1)/pi_n; NaN when unmeasurable
std::vector< T > alpha
stationary vector of A
The name-value contract of getSensitivityTable.
bool simulation
True when the callback is a simulator, which widens the default step.
double step
Relative step of the rate perturbation; negative selects the default.
std::string scheme
forward | central
std::string method
auto | exact | fd
One (station, class) row of the table.
What the table carries, plus the branch that produced it.
std::vector< SensRow< T > > rows
std::string method
"exact" or "fd", the branch actually taken
Every Cache node of the model, in node order; empty on a model with none.
std::vector< CacheNodeMetrics< T > > caches
One Cache node's measured behaviour.
std::vector< T > delayedhitqlen
(n) mean secondary requests waiting on the in-flight fetch of each item, and the same including the r...
std::vector< double > itemcap
(h) capacity of each list
std::vector< double > itemsize
(n) storage cost per item, EMPTY without setItemSizes
std::vector< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
std::vector< T > delayedprob
(K) delayed-hit fraction, EMPTY off a retrieval system
Matrix< T > hitproblist
(K x h) per-list hit fraction, EMPTY = not computed
std::size_t node
1-based node index of the Cache
std::vector< T > latency
(K) expected 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 caller-facing map_env knobs, options.config.map_env and friends.
std::size_t max_stages
0 = the transform's own default cap
The station table scattered to the NODE index space, plus the two flow columns the reference recomput...
@@NetworkSolver/getAvgSys: one response time and one throughput per chain.
std::vector< T > XN
(nchains) system throughput at the reference station
std::vector< T > CN
(nchains) system response time, i.e. the cycle time
Controls, defaulting to SolverOptions('SSA') in the reference.
std::size_t samples
Reaction firings to simulate; options.samples in the reference.
double warmupfrac
options.config.warmupfrac: the leading fraction of the path discarded before the means are taken.
std::string method
default and nrm both select the Next Reaction Method here.
unsigned long seed
options.seed; LINE's own default is 23000.
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.
std::shared_ptr< const qn::NetworkStruct< T > > solved_sn
The struct the path was simulated on when it differs from the caller's (ctmc_mapc widens a multiserve...
What the analyzer returns, in the same shape as the MVA and fluid results.
double simulated_time
Simulated time the metrics are averaged over; the reference's totalTime.
std::size_t samples
Reaction firings actually performed.
std::string method
The concrete algorithm, as the reference's method.
UQ.getInterval: the RANGE of every metric over the support of the Priors.
std::string method
mvainterval or sampled.
bool exact
True when the interval is the attained hull rather than a sampled range.
Matrix< T > Qlo
(nstations x nclasses) lower and upper endpoints of each metric.
std::string why
On the sampled path, the condition that disqualified the exact one.
T Xlo
System throughput and total response time; the EXACT path only.
UQ.defaultOptions plus the stream the Monte Carlo design draws from.
std::string method
default | dd | quad | mc, MATLAB UQ.listValidMethods.
std::size_t samples
Nodes per continuous Prior, or design points under mc; the reference's options.samples,...
unsigned long seed
The Monte Carlo stream; unread by a quadrature design, which draws nothing.
What solver_uq_run_analyzer returns.
std::vector< T > weights
The design weights, summing to 1.
std::string method
The RESOLVED discretization method: quad or mc.
std::vector< mva::AvgResult< T > > points
The result at each design point, in design order.
std::vector< PriorSite< T > > sites
Where the Priors were found.
std::vector< UqDesignPoint< T > > design
The alternatives each point substituted, one per site.
mva::AvgResult< T > avg
The prior-weighted expectation of every metric, (nstations x nclasses).
The inner solver's knobs, carried through untranslated.
double tol
< 0 = not given
double cutoff
ctmc open-population cutoff; < 0 = not given
std::string solver
mva | nc | mam | ba | ctmc | fluid | ssa.
Resolves the symbolic backend to use, and owns the container that serves it.
The stage solver of SolverUQ, named rather than passed.
Minimal RFC 6455 WebSocket server, enough to serve LineWebSocketServer's protocol.