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;
4154 if (g_json_output) {
4156 p[
"type"] =
"SamplePath";
4159 p[
"seed"] = sys.
seed;
4160 p[
"drawn"] = sys.
t.size();
4161 p[
"scope"] = k.node ? sn.
nodes[k.node - 1].name : std::string(
"(system)");
4162 if (k.node) p[
"node"] = k.node - 1;
4163 p[
"t"] = vector_json(sys.
t);
4164 p[
"event"] = index_json(sys.
event);
4165 p[
"state"] = matrix_json(sys.
state);
4174 w.push_back(sim.
run.space.empty() ? 0 : sim.
run.space[0].local[f].size());
4175 p[
"NodeWidths"] = w;
4177 p[
"sysAggr"] = matrix_json(sys.
aggr);
4179 p[
"nodeState"] = matrix_json(nodep.
state);
4180 p[
"nodeAggr"] = matrix_json(nodep.
aggr);
4182 emit_analysis<T>(
"sample", p,
"serial");
4185 std::printf(
"SolverSSA arith=%s method=serial events=%zu seed=%lu drawn=%zu scope=%s\n",
4187 k.node ? sn.
nodes[k.node - 1].name.c_str() :
"(system)");
4188 std::printf(
"%14s %8s %s\n",
"Time",
"Event",
4189 k.node ?
"SysAggregate | NodeState | NodeAggregate" :
"SysAggregate");
4190 for (std::size_t i = 0; i < sys.
t.size(); ++i) {
4191 std::printf(
"%14.8g %8zu ", sys.
t[i], sys.
event[i]);
4192 for (std::size_t c = 0; c < sys.
aggr.cols(); ++c)
4196 for (std::size_t c = 0; c < nodep.
state.cols(); ++c)
4199 for (std::size_t c = 0; c < nodep.
aggr.cols(); ++c)
4219 for (std::size_t i = 0; i < m.
rows(); ++i)
4220 for (std::size_t j = 0; j < m.
cols(); ++j)
4246int solve_model_ssa(
const std::string& file,
const Knobs& k) {
4249 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4250 if (k.samples) opt.
samples = k.samples;
4251 if (k.seed) opt.
seed = k.seed;
4252 if (k.warmupfrac >= 0.0) opt.
warmupfrac = k.warmupfrac;
4258 std::vector<line::ssa::SsaCacheRatio> cacheratio;
4270 std::printf(
"SolverSSA arith=%s method=%s type=%s samples=%zu seed=%lu time=%.6g\n",
4291 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
4301 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
4306 row.w = line::num_traits<T>::to_double(WN(i, c));
4310 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
4312 : line::num_traits<T>::to_double(AN(i, c));
4322 if (!k.method.empty()) opt.
method = k.method;
4323 if (k.tol >= 0.0) opt.
tol = k.tol;
4324 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4325 if (k.iter_max >= 0) opt.
iter_max =
static_cast<std::size_t
>(k.iter_max);
4331 if (k.pstar > 0.0) {
4332 opt.
pstar = k.pstar;
4352int solve_model_fluid(
const std::string& file,
const Knobs& k) {
4370 std::printf(
"SolverFluid arith=%s method=%s type=%s iters=%d\n",
4392 &refreshed, &cache);
4413 if (!cache.
empty()) extra[
"Cache"] = cache_extra_json<T>(cache);
4414 emit_avg_table<T>(sn, r.
method, [&](std::size_t i, std::size_t c) {
4419 row.w = line::num_traits<T>::to_double(WN(i, c));
4425 row.a = sn.stations[i].sched == line::lang::SchedStrategy::EXT
4427 : line::num_traits<T>::to_double(AN(i, c));
4448int solve_model_fluid_statevec(
const std::string& file,
const Knobs& k) {
4455 "-a statevec reports the converged ODE state and this solve produced none; the rmf "
4456 "and closing branches integrate a drift and fill it, so a branch that returns means "
4457 "directly has no state vector to report");
4461 std::vector<std::size_t> ist, cls, phs;
4462 for (std::size_t i = 0; i < sn.
nstations; ++i)
4463 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4464 const std::size_t np = sn.
phases_of(i + 1, c + 1);
4465 for (std::size_t j = 0; j < np; ++j) {
4474 const bool labelled = ist.size() == r.
xvec.size();
4476 if (g_json_output) {
4478 p[
"type"] =
"FluidStateVec";
4480 p[
"xvec"] = vector_json(r.
xvec);
4481 p[
"labelled"] = labelled;
4483 line::reg::Json st = line::reg::Json::array(), cl = line::reg::Json::array(),
4484 ph = line::reg::Json::array();
4485 for (std::size_t j = 0; j < ist.size(); ++j) {
4486 st.push_back(sn.
stations[ist[j]].name);
4487 cl.push_back(sn.
classes[cls[j]].name);
4488 ph.push_back(phs[j]);
4494 emit_analysis<T>(
"statevec", p, r.
method);
4500 std::printf(
"# the ODE state is %zu wide and the (station, class, phase) layout accounts "
4501 "for %zu; the coordinates are printed unlabelled\n",
4502 r.
xvec.size(), ist.size());
4503 std::printf(
"%-10s %20s\n",
"Index",
"x");
4504 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
4505 std::printf(
"%-10zu %20.10g\n", j, r.
xvec[j]);
4508 std::printf(
"%-16s %-14s %-8s %20s\n",
"Station",
"JobClass",
"Phase",
"x");
4509 for (std::size_t j = 0; j < r.
xvec.size(); ++j)
4510 std::printf(
"%-16s %-14s %-8zu %20.10g\n", sn.
stations[ist[j]].name.c_str(),
4511 sn.
classes[cls[j]].name.c_str(), phs[j], r.
xvec[j]);
4549 line::env::dispatch_detail::env_take_statevec(out);
4560int solve_model_env(
const std::string& file,
const Knobs& k) {
4563 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
4564 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
4565 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
4567 if (k.tran_points) opt.
tran_points = k.tran_points;
4568 if (k.tol >= 0.0) opt.
stage.
tol = k.tol;
4586 if (!k.stage_solver.empty()) opt.
stage_solver = k.stage_solver;
4607 std::printf(
"SolverENV arith=%s method=%s stages=%zu (closed-form limit)\n",
4609 else if (r.
method ==
"statevec")
4610 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g iters=%d%s\n",
4617 std::printf(
"SolverENV arith=%s method=%s stages=%zu horizon=%.6g points=%zu iters=%d%s\n",
4625 if (!r.
cache.empty()) extra[
"Cache"] = cache_extra_json<T>(r.
cache);
4628 [&](std::size_t i, std::size_t c) {
4630 row.q = line::num_traits<T>::to_double(r.QN(i, c));
4631 row.u = line::num_traits<T>::to_double(r.UN(i, c));
4632 row.t = line::num_traits<T>::to_double(r.TN(i, c));
4633 row.r = std::numeric_limits<double>::quiet_NaN();
4634 row.a = std::numeric_limits<double>::quiet_NaN();
4635 row.w = row.q / row.t;
4639 if (!g_json_output) print_cache_rows<T>(sn, r.
cache);
4656int solve_model_fluid_var(
const std::string& file,
const Knobs& k) {
4663 "-a var needs a fluid method that computes a second moment: 'minnormal', 'refined' or "
4664 "'dae' for the stationary covariance, 'kp' for the covariance along the trajectory. "
4666 r.
method +
"' method integrates the mean only");
4668 if (g_json_output) {
4670 p[
"type"] =
"QueueLengthVariance";
4672 p[
"Station"] = line::reg::Json::array();
4673 p[
"JobClass"] = line::reg::Json::array();
4674 p[
"QVar"] = line::reg::Json::array();
4675 p[
"QStd"] = line::reg::Json::array();
4676 for (std::size_t i = 0; i < sn.
nstations; ++i)
4677 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4679 p[
"Station"].push_back(sn.
stations[i].name);
4680 p[
"JobClass"].push_back(sn.
classes[c].name);
4685 emit_analysis<T>(
"var", p, r.
method);
4690 std::printf(
"%-16s %-14s %12s %12s\n",
"Station",
"JobClass",
"QVar",
"QStd");
4691 for (std::size_t i = 0; i < sn.
nstations; ++i)
4692 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4694 std::printf(
"%-16s %-14s %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4710int solve_model_fluid_odes(
const std::string& file,
const Knobs& k) {
4713 if (!k.method.empty()) opt.
method = k.method;
4714 if (k.tol >= 0.0) opt.
tol = k.tol;
4721 const std::string notation = k.notation.empty() ?
"scalar" : k.notation;
4723 if (g_json_output) {
4726 p[
"notation"] = notation;
4731 emit_analysis<T>(
"odes", p,
4732 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
4735 std::printf(
"%s\n", tex.c_str());
4751int solve_model_fluid_jacobian(
const std::string& file,
const Knobs& k) {
4754 if (!k.method.empty()) opt.
method = k.method;
4761 std::string m = opt.
method;
4762 if (m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
4766 if (!k.symbolic.empty()) symopt.
backend = k.symbolic;
4770 if (g_json_output) {
4772 p[
"type"] =
"Jacobian";
4773 p[
"engine"] = jac.
engine;
4777 for (std::size_t i = 0; i < jac.
J.size(); ++i) rows.push_back(
line::reg::Json(jac.
J[i]));
4778 p[
"jacobian"] = rows;
4783 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e) {
4785 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4787 one[it->first] = it->second;
4790 p[
"equilibria"] = eqs;
4791 emit_analysis<T>(
"jacobian", p,
4792 line::fluid::detail::fluid_resolve_method(sn, opt.
method, opt));
4796 std::printf(
"engine=%s states=%zu\n", jac.
engine.c_str(), jac.
vars.size());
4797 for (std::size_t i = 0; i < jac.
rhs.size(); ++i)
4798 std::printf(
"d%s/dt = %s\n", jac.
vars[i].c_str(), jac.
rhs[i].c_str());
4799 for (std::size_t i = 0; i < jac.
J.size(); ++i)
4800 for (std::size_t j = 0; j < jac.
J[i].size(); ++j) {
4803 std::printf(
"J[%s,%s] = %s\n", jac.
vars[i].c_str(), jac.
vars[j].c_str(),
4804 jac.
J[i][j].c_str());
4808 std::printf(
"equilibria: none in closed form (the solve found none, which is not a "
4809 "proof that none exist)\n");
4810 for (std::size_t e = 0; e < jac.
equilibria.size(); ++e)
4811 for (std::map<std::string, std::string>::const_iterator it = jac.
equilibria[e].begin();
4813 std::printf(
"equilibrium %zu: %s = %s\n", e + 1, it->first.c_str(),
4814 it->second.c_str());
4831int solve_model_fluid_tranvar(
const std::string& file,
const Knobs& k) {
4839 if (g_json_output) {
4841 p[
"type"] =
"TranAvgVarTable";
4843 p[
"t0"] = tr.t.front();
4844 p[
"t1"] = tr.t.back();
4846 for (std::size_t j = 0; j < tr.t.size(); ++j) ts.push_back(tr.t[j]);
4849 for (std::size_t i = 0; i < sn.
nstations; ++i)
4850 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4853 e[
"Station"] = sn.
stations[i].name;
4854 e[
"JobClass"] = sn.
classes[c].name;
4858 for (std::size_t j = 0; j < tr.QVar.size(); ++j) v.push_back(tr.QVar[j](i, c));
4867 for (std::size_t j = 0; j < tr.Sigma.size(); ++j)
4868 sig.push_back(matrix_json<double>(tr.Sigma[j]));
4870 emit_analysis<T>(
"tranvar", p,
"kp");
4873 std::printf(
"SolverFluid arith=%s method=kp tspan=[%g,%g] points=%zu dim=%zu\n",
4875 tr.Sigma.empty() ? std::size_t(0) : tr.Sigma.front().rows());
4876 std::printf(
"%-16s %-14s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QVar",
"QStd");
4877 for (std::size_t i = 0; i < sn.
nstations; ++i)
4878 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4880 for (std::size_t j = 0; j < tr.QVar.size(); ++j) {
4881 const double var = tr.QVar[j](i, c);
4882 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g\n", sn.
stations[i].name.c_str(),
4883 sn.
classes[c].name.c_str(), tr.t[j], var,
4884 var >= 0.0 ? std::sqrt(var) : std::numeric_limits<double>::quiet_NaN());
4909int solve_model_fluid_tran(
const std::string& file,
const Knobs& k) {
4913 const std::vector<line::fluid::FluidTranPoint> tr =
4917 if (g_json_output) {
4919 p[
"type"] =
"TranAvgTable";
4922 p[
"t1"] = tr.back().t;
4924 for (std::size_t j = 0; j < tr.size(); ++j) ts.push_back(tr[j].t);
4926 for (std::size_t i = 0; i < sn.
nstations; ++i)
4927 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4930 e[
"Station"] = sn.
stations[i].name;
4931 e[
"JobClass"] = sn.
classes[c].name;
4935 line::reg::Json q = line::reg::Json::array(), u = line::reg::Json::array(),
4936 x = line::reg::Json::array();
4937 for (std::size_t j = 0; j < tr.size(); ++j) {
4938 q.push_back(tr[j].
QN(i, c));
4939 u.push_back(tr[j].
UN(i, c));
4940 x.push_back(tr[j].TN(i, c));
4948 emit_analysis<T>(
"tran", p,
"closing");
4951 std::printf(
"SolverFluid arith=%s method=closing tspan=[0,%g] points=%zu\n",
4953 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
4955 for (std::size_t i = 0; i < sn.
nstations; ++i)
4956 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
4958 for (std::size_t j = 0; j < tr.size(); ++j)
4959 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
4961 tr[j].QN(i, c), tr[j].UN(i, c), tr[j].TN(i, c));
4981int solve_model_fluid_prob(
const std::string& file,
const Knobs& k) {
4988 for (std::size_t i = 0; i < sn.
nstations; ++i)
4991 if (g_json_output) {
4993 p[
"type"] =
"ProbAggr";
4995 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
4996 lp = line::reg::Json::array();
4997 for (std::size_t i = 0; i < sn.
nstations; ++i) {
4999 pa.push_back(pr[i]);
5000 lp.push_back(lg[i]);
5004 p[
"logProbAggr"] = lp;
5005 emit_analysis<T>(
"prob", p, r.
method);
5010 std::printf(
"%-16s %14s %14s\n",
"Station",
"ProbAggr",
"logProbAggr");
5011 for (std::size_t i = 0; i < sn.
nstations; ++i)
5012 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(), pr[i], lg[i]);
5027int solve_model_fluid_cdf(
const std::string& file,
const Knobs& k) {
5031 const std::vector<std::vector<line::fluid::FluidPassage> > RD =
5034 if (g_json_output) {
5036 p[
"type"] =
"CdfRespT";
5039 for (std::size_t i = 0; i < RD.size(); ++i)
5040 for (std::size_t c = 0; c < RD[i].size(); ++c) {
5044 if (RD[i][c].t.empty())
continue;
5046 e[
"Station"] = sn.
stations[i].name;
5047 e[
"JobClass"] = sn.
classes[c].name;
5055 emit_analysis<T>(
"cdf", p, std::string());
5059 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5060 for (std::size_t i = 0; i < RD.size(); ++i)
5061 for (std::size_t c = 0; c < RD[i].size(); ++c)
5062 for (std::size_t j = 0; j < RD[i][c].t.size(); ++j)
5063 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
5064 sn.
classes[c].name.c_str(), RD[i][c].t[j], RD[i][c].cdf[j]);
5083int solve_model_fluid_aoi(
const std::string& file,
const Knobs& k) {
5089 "-a aoi reports the age of a status-update system and needs the topology the age laws "
5090 "are defined for: " +
5091 (top.
error.empty() ? std::string(
"this model is not one") : top.
error));
5093 const std::string requested = line::fluid::detail::fluid_unqualify(opt.
method);
5094 if (requested !=
"default" && requested !=
"mfq")
5096 "-a aoi is the AoI branch of the 'mfq' method; '" + requested +
5097 "' integrates the mean-field drift and carries no age process");
5102 "-a aoi: the 'mfq' method did not take its AoI branch on this model");
5107 const std::size_t np = 200;
5108 std::vector<double> tv(np), fa(np), fp(np);
5109 for (std::size_t j = 0; j < np; ++j) {
5110 tv[j] = 5.0 * base *
static_cast<double>(j) /
static_cast<double>(np - 1);
5114 const double asd = std::sqrt(std::max(0.0, r.
aoi.
aoi.
var));
5115 const double psd = std::sqrt(std::max(0.0, r.
aoi.
paoi.
var));
5117 if (g_json_output) {
5138 p[
"AoI_A"] = matrix_json(r.
aoi.
aoi.
A);
5141 p[
"PAoI_A"] = matrix_json(r.
aoi.
paoi.
A);
5143 emit_analysis<T>(
"aoi", p, r.
method);
5146 std::printf(
"SolverFluid arith=%s method=mfq system=%s preemption=%.6g\n",
5148 std::printf(
"%-8s %14s %14s %14s\n",
"Metric",
"Mean",
"Var",
"Std");
5151 std::printf(
"%14s %14s %14s\n",
"Time",
"F_AoI(t)",
"F_PAoI(t)");
5152 for (std::size_t j = 0; j < np; ++j)
5153 std::printf(
"%14.8g %14.10g %14.10g\n", tv[j], fa[j], fp[j]);
5164int solve_model_prob(
const std::string& file,
const Knobs& k) {
5167 "the -a prob analysis fits a binomial/product-form law and needs transcendental "
5168 "arithmetic; rerun with --arith double or --arith real");
5172 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5173 if (k.tol >= 0.0) opt.
tol = k.tol;
5174 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5175 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5181 if (g_json_output) {
5183 p[
"type"] =
"ProbAggr";
5186 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array();
5187 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5201 std::printf(
"%-16s %14s\n",
"Station",
"ProbAggr");
5202 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5205 std::printf(
"%-16s %14.10g\n", sn.
stations[i].name.c_str(),
5229int solve_model_marg(
const std::string& file,
const Knobs& k) {
5232 "the -a marg analysis fits a binomial / Poisson / geometric law and needs "
5233 "transcendental arithmetic; rerun with --arith double or --arith real");
5237 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5238 if (k.tol >= 0.0) opt.
tol = k.tol;
5239 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5240 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5245 std::vector<std::size_t> ists;
5249 " exceeds the number of nodes in the model (" +
5251 const std::size_t ist = sn.
nodes[k.node - 1].station;
5254 sn.
nodes[k.node - 1].name +
5255 "') is not a station, and a queue-length distribution is "
5256 "reported per station");
5257 ists.push_back(ist);
5259 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
5261 std::vector<std::size_t> rs;
5265 " exceeds the number of classes in the model");
5266 rs.push_back(k.jobclass);
5268 for (std::size_t c = 0; c < sn.
nclasses; ++c) rs.push_back(c + 1);
5273 std::vector<line::mva::MargResult<T> > curves;
5274 for (std::size_t a = 0; a < ists.size(); ++a)
5275 for (std::size_t b = 0; b < rs.size(); ++b)
5277 k.marg_states, opt.
method));
5279 if (g_json_output) {
5281 p[
"type"] =
"ProbMarg";
5284 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
5285 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
5288 e[
"station"] = ists[a] - 1;
5289 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5290 e[
"jobclass"] = rs[b] - 1;
5291 e[
"JobClass"] = sn.
classes[rs[b] - 1].name;
5293 for (std::size_t n = 0; n < m.
P.size(); ++n)
5294 jobs.push_back(k.marg_states.empty() ?
static_cast<long>(n)
5295 : k.marg_states[n]);
5297 e[
"P"] = vector_json(m.
P);
5298 e[
"logP"] = vector_json(m.
logP);
5301 p[
"marginal"] = arr;
5308 std::printf(
"%-16s %-14s %-8s %16s\n",
"Station",
"JobClass",
"Jobs",
"ProbMarg");
5309 for (std::size_t a = 0, q = 0; a < ists.size(); ++a)
5310 for (std::size_t b = 0; b < rs.size(); ++b, ++q) {
5312 for (std::size_t n = 0; n < m.
P.size(); ++n)
5313 std::printf(
"%-16s %-14s %-8ld %16.10g\n",
5314 sn.
stations[ists[a] - 1].name.c_str(),
5315 sn.
classes[rs[b] - 1].name.c_str(),
5316 k.marg_states.empty() ?
static_cast<long>(n) : k.marg_states[n],
5355int solve_model_nc_busyp(
const std::string& file,
const Knobs& k) {
5356 if (k.busy_subnet.empty())
5358 "-a busyperiod needs --busyperiod-subnet: the busy period is defined for a named "
5359 "subnetwork of stations, and no default can choose one");
5362 std::vector<std::size_t> subnet;
5363 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t) {
5366 std::to_string(k.busy_subnet[t]) +
", beyond the model's " +
5368 subnet.push_back(k.busy_subnet[t] - 1);
5370 std::vector<std::size_t> orders = k.busy_orders;
5371 if (orders.empty()) orders.push_back(1);
5374 if (g_json_output) {
5376 p[
"type"] =
"BusyPeriod";
5379 for (std::size_t t = 0; t < subnet.size(); ++t) sj.push_back(subnet[t]);
5381 for (std::size_t t = 0; t < orders.size(); ++t) oj.push_back(orders[t]);
5383 for (std::size_t t = 0; t < b.size(); ++t) bj.push_back(b[t]);
5387 emit_analysis<T>(
"busyperiod", p,
"daduna");
5391 for (std::size_t t = 0; t < k.busy_subnet.size(); ++t)
5392 std::printf(
"%s%zu", t ?
"," :
"", k.busy_subnet[t]);
5394 for (std::size_t t = 0; t < orders.size(); ++t)
5395 std::printf(
" order %zu %.10g\n", orders[t], b[t]);
5400int solve_model_normconst(
const std::string& file,
const Knobs& k,
const std::string& solver) {
5405 if (solver ==
"nc") {
5407 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5408 if (k.tol >= 0.0) opt.
tol = k.tol;
5409 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5410 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5413 : std::numeric_limits<double>::quiet_NaN();
5417 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5418 if (k.tol >= 0.0) opt.
tol = k.tol;
5419 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5420 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5424 if (g_json_output) {
5426 p[
"type"] =
"NormConst";
5429 p[
"logNormConstAggr"] = lG;
5430 emit_analysis<T>(
"normconst", p, method);
5433 std::printf(
"Solver%s arith=%s method=%s lognormconst=%.10g\n",
5443 for (std::size_t i = 0; i < sn.
nstations; ++i)
5444 for (std::size_t r = 0; r < sn.
nclasses; ++r)
5460int solve_model_nc_prob(
const std::string& file,
const Knobs& k) {
5463 "the -s nc -a prob analysis exponentiates a difference of log normalizing constants "
5464 "and needs transcendental arithmetic; rerun with --arith double or --arith real");
5468 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5469 if (k.tol >= 0.0) opt.
tol = k.tol;
5470 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5471 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5488 if (!k.state.empty()) {
5491 "--state is the state of ONE node and needs --node to say which");
5492 const std::size_t ist =
5493 k.node <= sn.
nodes.size() ? sn.
nodes[k.node - 1].station : 0;
5496 " is not a station, so it has no queue-length state");
5499 for (std::size_t c = 0; c < sn.
nclasses; ++c) {
5504 std::vector<T> row(k.state.size());
5505 for (std::size_t i = 0; i < k.state.size(); ++i)
5509 for (std::size_t c = 0; c < sn.
nclasses; ++c)
5549 std::numeric_limits<double>::quiet_NaN());
5557 std::numeric_limits<double>::quiet_NaN());
5567 if (g_json_output) {
5569 p[
"type"] =
"ProbAggr";
5573 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
5577 line::reg::Json pa = line::reg::Json::array(), pm = line::reg::Json::array();
5578 for (std::size_t i = 0; i < sn.
nstations; ++i) {
5585 emit_analysis<T>(
"prob", p, am);
5592 std::printf(
"ProbSys, Prob and ProbAggr need a load-dependent normalizing constant, "
5593 "which method '%s' does not compute\n",
5598 std::printf(
"%-16s %14s %14s\n",
"Station",
"Prob",
"ProbAggr");
5599 for (std::size_t i = 0; i < sn.
nstations; ++i)
5600 std::printf(
"%-16s %14.10g %14.10g\n", sn.
stations[i].name.c_str(),
5623int solve_model_nc_sysmarg(
const std::string& file,
const Knobs& k) {
5626 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5627 if (k.tol >= 0.0) opt.
tol = k.tol;
5631 for (std::size_t r = 0; r < sn.
nclasses; ++r) {
5632 const double pop = sn.
classes[r].population;
5633 if (!std::isfinite(pop))
5635 "getProbSysMarg requires a closed model: the joint law of the total queue lengths "
5636 "is not defined when a class has an infinite population");
5640 static_cast<int>(sn.
nstations),
static_cast<int>(std::llround(Ntot)));
5642 std::vector<double> P(states.size(), 0.0);
5643 for (std::size_t j = 0; j < states.size(); ++j)
5647 if (g_json_output) {
5649 p[
"type"] =
"ProbSysMarg";
5651 p[
"engine"] = k.method_perm;
5652 line::reg::Json st = line::reg::Json::array(), arr = line::reg::Json::array();
5653 for (std::size_t i = 0; i < sn.
nstations; ++i) st.push_back(sn.
stations[i].name);
5654 for (std::size_t j = 0; j < states.size(); ++j) {
5657 for (std::size_t i = 0; i < sn.
nstations; ++i) n.push_back(states[j][i]);
5664 emit_analysis<T>(
"sysmarg", p, opt.
method);
5668 opt.
method.c_str(), k.method_perm.c_str());
5669 for (std::size_t i = 0; i < sn.
nstations; ++i)
5670 std::printf(
"%10s", sn.
stations[i].name.c_str());
5671 std::printf(
" %14s\n",
"ProbSysMarg");
5673 for (std::size_t j = 0; j < states.size(); ++j) {
5674 for (std::size_t i = 0; i < sn.
nstations; ++i) std::printf(
"%10d", states[j][i]);
5675 std::printf(
" %14.10g\n", P[j]);
5678 std::printf(
"%*s %14.10g\n",
static_cast<int>(10 * sn.
nstations),
"sum", total);
5700int solve_model_nc_marg(
const std::string& file,
const Knobs& k) {
5703 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5704 if (k.tol >= 0.0) opt.
tol = k.tol;
5705 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5706 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5709 std::vector<std::size_t> ists;
5713 " exceeds the number of nodes in the model (" +
5715 const std::size_t ist = sn.
nodes[k.node - 1].station;
5718 sn.
nodes[k.node - 1].name +
5719 "') is not a station, and a queue-length distribution is "
5720 "reported per station");
5721 ists.push_back(ist);
5723 for (std::size_t i = 0; i < sn.
nstations; ++i) ists.push_back(i + 1);
5728 std::vector<line::nc::NcQueueLengthDist<T> > curves;
5729 for (std::size_t a = 0; a < ists.size(); ++a)
5732 if (g_json_output) {
5734 p[
"type"] =
"ProbMargAggr";
5737 for (std::size_t a = 0; a < ists.size(); ++a) {
5739 e[
"station"] = ists[a] - 1;
5740 e[
"Station"] = sn.
stations[ists[a] - 1].name;
5741 e[
"P"] = vector_json<T>(curves[a].P);
5742 e[
"logP"] = vector_json<T>(curves[a].logP);
5746 emit_analysis<T>(
"marg", p, opt.
method);
5751 for (std::size_t a = 0; a < ists.size(); ++a) {
5752 std::printf(
"%-16s %-8s %14s %14s\n",
"Station",
"n",
"P",
"logP");
5753 for (std::size_t n = 0; n < curves[a].P.size(); ++n)
5754 std::printf(
"%-16s %-8zu %14.10g %14.10g\n", sn.
stations[ists[a] - 1].name.c_str(), n,
5777int solve_model_nc_cdf(
const std::string& file,
const Knobs& k) {
5780 "the -s nc -a cdf analysis evaluates the sojourn law on a logarithmic time grid and "
5781 "inverts a generating function; it needs transcendental arithmetic, so rerun with "
5782 "--arith double or --arith real");
5786 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5787 if (k.tol >= 0.0) opt.
tol = k.tol;
5788 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5789 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5790 if (!k.cdf_algorithm.empty()) opt.
cdf_algorithm = k.cdf_algorithm;
5793 if (!r.
warning.empty()) std::fprintf(stderr,
"warning: %s\n", r.
warning.c_str());
5795 if (g_json_output) {
5797 p[
"type"] =
"CdfRespT";
5804 for (std::size_t i = 0; i < r.
RD.size(); ++i)
5805 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
5809 if (r.
RD[i][c].empty())
continue;
5811 e[
"Station"] = sn.
stations[i].name;
5812 e[
"JobClass"] = sn.
classes[c].name;
5815 line::reg::Json tt = line::reg::Json::array(), ff = line::reg::Json::array();
5816 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j) {
5825 p[
"tset"] = vector_json(r.
tset);
5830 emit_analysis<T>(
"cdf", p, std::string());
5835 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
5836 for (std::size_t i = 0; i < r.
RD.size(); ++i)
5837 for (std::size_t c = 0; c < r.
RD[i].size(); ++c) {
5838 if (r.
RD[i][c].empty())
continue;
5839 for (std::size_t j = 0; j < r.
RD[i][c].rows(); ++j)
5840 std::printf(
"%-16s %-14s %14.8g %14.10g\n", sn.
stations[i].name.c_str(),
5861double sens_sanitize_signed(
double x) {
5889int solve_model_nc_sens(
const std::string& file,
const Knobs& k) {
5892 if (!k.method.empty() && k.method !=
"default") opt.
method = k.method;
5893 if (k.tol >= 0.0) opt.
tol = k.tol;
5894 if (k.iter_tol >= 0.0) opt.
iter_tol = k.iter_tol;
5895 if (k.iter_max >= 0) opt.
iter_max = k.iter_max;
5899 if (!k.sens_method.empty()) so.
method = k.sens_method;
5900 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
5901 if (k.sens_step > 0.0) so.
step = k.sens_step;
5907 sn, so,
true, [&sn, &opt]() {
5921 if (g_json_output) {
5923 p[
"type"] =
"SensitivityTable";
5925 p[
"branch"] = tbl.
method;
5943 emit_analysis<T>(
"sens", p, tbl.
method ==
"fd" ? opt.
method : std::string());
5950 std::printf(
"%-16s %-14s %14s %14s %14s %14s\n",
"Station",
"JobClass",
"dTput_dRate",
5951 "dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
5953 std::printf(
"%-16s %-14s %14.6g %14.6g %14.6g %14.6g\n", r.
station.c_str(),
5981 if (k.samples) o.
samples = k.samples;
5982 if (k.seed) o.
seed =
static_cast<long>(k.seed);
5983 if (!k.method.empty()) o.
method = k.method;
5984 if (!k.ldes_tranfilter.empty()) o.
tranfilter = k.ldes_tranfilter;
5985 if (k.ldes_warmupfrac >= 0.0) o.
warmupfrac = k.ldes_warmupfrac;
5986 if (!k.ldes_cimethod.empty()) o.
cimethod = k.ldes_cimethod;
5987 if (k.ldes_cnvgon) o.
cnvgon =
true;
5988 if (k.ldes_cnvgtol > 0.0) o.
cnvgtol = k.ldes_cnvgtol;
5989 if (k.ldes_slotted) o.
slotted =
true;
5990 if (k.ldes_slotlength > 0.0) {
5994 if (k.ldes_replications > 0) o.
replications = k.ldes_replications;
5995 if (k.ldes_numthreads > 0) o.
numthreads = k.ldes_numthreads;
5996 if (k.ldes_maxtime > 0.0) o.
timeout = k.ldes_maxtime;
5997 if (!k.ldes_initsol.empty()) o.
init_sol = k.ldes_initsol;
5998 if (!k.ldes_rest_url.empty()) o.
rest_url = k.ldes_rest_url;
6011inline std::string ldes_document(
const std::string& file) {
6012 return file.empty() ? stdin_model_text() :
line::ldes::detail::read_file(file);
6017 return i < M.
rows() && j < M.
cols() ? M(i, j) : 0.0;
6031 const std::vector<std::string>& extra) {
6035 "SolverLDES exceeded its wall-clock budget (--ldes-maxtime) and was terminated before "
6036 "it wrote a result; raise the budget or lower --samples");
6039 "SolverLDES: the engine reported no station names, so its metrics cannot be labelled; "
6040 "the run produced no result document");
6054 std::printf(
"SolverLDES arith=double method=%s type=%s engine=%s samples=%zu seed=%ld "
6055 "time=%.6g events=%lld stopping=%s\n",
6085int solve_model_ldes_avg(
const std::string& file,
const Knobs& k) {
6088 if (!g_json_output) ldes_banner(r, o);
6095 if (!r.
QNCI.
empty()) ci[
"QNCI"] = matrix_json<double>(r.
QNCI);
6096 if (!r.
UNCI.
empty()) ci[
"UNCI"] = matrix_json<double>(r.
UNCI);
6097 if (!r.
RNCI.
empty()) ci[
"RNCI"] = matrix_json<double>(r.
RNCI);
6098 if (!r.
TNCI.
empty()) ci[
"TNCI"] = matrix_json<double>(r.
TNCI);
6099 if (!r.
ANCI.
empty()) ci[
"ANCI"] = matrix_json<double>(r.
ANCI);
6100 if (!r.
WNCI.
empty()) ci[
"WNCI"] = matrix_json<double>(r.
WNCI);
6101 if (!ci.empty()) extra[
"CI"] = ci;
6105 f[
"QNfcr"] = matrix_json<double>(r.
QNfcr);
6106 f[
"RNfcr"] = matrix_json<double>(r.
RNfcr);
6107 f[
"TNfcr"] = matrix_json<double>(r.
TNfcr);
6108 f[
"WNfcr"] = matrix_json<double>(r.
WNfcr);
6117 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
6121 if (!it->second.hit.empty()) c[
"hit"] = matrix_json<double>(it->second.hit);
6122 if (!it->second.delayed.empty()) c[
"delayed"] = matrix_json<double>(it->second.delayed);
6123 if (!it->second.miss.empty()) c[
"miss"] = matrix_json<double>(it->second.miss);
6124 if (!it->second.latency.empty()) c[
"latency"] = matrix_json<double>(it->second.latency);
6125 if (!it->second.hitList.empty()) c[
"hitList"] = matrix_json<double>(it->second.hitList);
6126 if (!it->second.itemProb.empty())
6127 c[
"itemProb"] = matrix_json<double>(it->second.itemProb);
6128 if (!it->second.listCost.empty())
6129 c[
"listCost"] = matrix_json<double>(it->second.listCost);
6132 extra[
"cacheMetrics"] = cm;
6153 for (std::size_t j = 0; j < sn.
nstations; ++j)
6155 std::vector<std::size_t> cl_of(r.
class_names.size(), 0);
6156 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6157 for (std::size_t k = 0; k < sn.
nclasses; ++k)
6161 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6162 if (st_of[i] && cl_of[c]) RNs(st_of[i] - 1, cl_of[c] - 1) = ldes_at(r.
RN, i, c);
6165 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6166 WNd(i, c) = (st_of[i] && cl_of[c]) ? WNs(st_of[i] - 1, cl_of[c] - 1)
6167 : ldes_at(r.
WN, i, c);
6171 [&](std::size_t i, std::size_t c) {
6173 v.q = ldes_at(r.QN, i, c);
6174 v.u = ldes_at(r.UN, i, c);
6175 v.r = ldes_at(r.RN, i, c);
6177 v.a = ldes_at(r.AN, i, c);
6178 v.t = ldes_at(r.TN, i, c);
6181 extra, ldes_envelope(r, o));
6184 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s\n",
"Region",
"JobClass",
"QLen",
"RespT",
6185 "Tput",
"Weight",
"MemOcc");
6186 for (std::size_t i = 0; i < r.
QNfcr.
rows(); ++i)
6187 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6188 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
6189 (
"Region" + std::to_string(i + 1)).c_str(), r.
class_names[c].c_str(),
6190 ldes_at(r.
QNfcr, i, c), ldes_at(r.
RNfcr, i, c),
6195 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Cache",
"JobClass",
"Hit",
"Delayed",
6197 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
6200 for (std::size_t c = 0; c < r.
class_names.size(); ++c)
6201 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n", it->first.c_str(),
6202 r.
class_names[c].c_str(), ldes_at(it->second.hit, 0, c),
6203 ldes_at(it->second.delayed, 0, c), ldes_at(it->second.miss, 0, c),
6204 ldes_at(it->second.latency, 0, c));
6223int solve_model_ldes_tran(
const std::string& file,
const Knobs& k) {
6228 std::vector<std::string> extra;
6229 extra.push_back(
"--trajectory");
6231 if (r.
t.empty() || r.
QNt.empty())
6233 "SolverLDES -a tran produced no trajectory: the engine ran but recorded no bucket over "
6234 "[" + line::ldes::detail::shortest(k.t0) +
"," +
6235 line::ldes::detail::shortest(k.t1) +
"]");
6237 if (g_json_output) {
6239 p[
"type"] =
"TranAvgTable";
6244 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
6245 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
6248 if (r.
QNt[i][c].empty())
continue;
6251 :
"Station" + std::to_string(i);
6256 line::reg::Json tt = line::reg::Json::array(), q = line::reg::Json::array(),
6257 u = line::reg::Json::array(), x = line::reg::Json::array();
6258 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
6259 tt.push_back(r.
QNt[i][c](j, 1));
6260 q.push_back(r.
QNt[i][c](j, 0));
6262 if (i < r.
UNt.size() && c < r.
UNt[i].size())
6263 for (std::size_t j = 0; j < r.
UNt[i][c].rows(); ++j)
6264 u.push_back(r.
UNt[i][c](j, 0));
6265 if (i < r.
TNt.size() && c < r.
TNt[i].size())
6266 for (std::size_t j = 0; j < r.
TNt[i][c].rows(); ++j)
6267 x.push_back(r.
TNt[i][c](j, 0));
6272 curves.push_back(e);
6274 p[
"curves"] = curves;
6275 p[
"tset"] = vector_json(r.
t);
6280 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6281 p[it.key()] = it.value();
6282 emit_analysis<double>(
"tran", p, r.
method);
6286 std::printf(
"%-16s %-14s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"Time",
"QLen",
"Util",
6288 for (std::size_t i = 0; i < r.
QNt.size(); ++i)
6289 for (std::size_t c = 0; c < r.
QNt[i].size(); ++c) {
6290 if (r.
QNt[i][c].empty())
continue;
6291 for (std::size_t j = 0; j < r.
QNt[i][c].rows(); ++j) {
6292 const bool hu = i < r.
UNt.size() && c < r.
UNt[i].size() &&
6293 j < r.
UNt[i][c].rows();
6294 const bool hx = i < r.
TNt.size() && c < r.
TNt[i].size() &&
6295 j < r.
TNt[i][c].rows();
6296 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g\n",
6299 r.
QNt[i][c](j, 1), r.
QNt[i][c](j, 0),
6300 hu ? r.
UNt[i][c](j, 0) : 0.0, hx ? r.
TNt[i][c](j, 0) : 0.0);
6319int solve_model_ldes_cdf(
const std::string& file,
const Knobs& k,
const char* key,
6322 std::vector<std::string> extra;
6323 extra.push_back(
"--respt-samples");
6327 "SolverLDES -a cdf needs the per-job response times the engine records under "
6328 "--respt-samples and the run returned none; raise --samples so completions are "
6333 std::vector<std::vector<std::vector<double>>> tt(r.
respTimeSamples.size()), ff(
6340 if (x.empty())
continue;
6341 std::sort(x.begin(), x.end());
6342 const double n =
static_cast<double>(x.size());
6343 for (std::size_t j = 0; j < x.size(); ++j) {
6344 if (j + 1 < x.size() && x[j + 1] == x[j])
continue;
6345 tt[i][c].push_back(x[j]);
6346 ff[i][c].push_back(
static_cast<double>(j + 1) / n);
6351 if (g_json_output) {
6355 p[
"algorithm"] =
"empirical";
6357 for (std::size_t i = 0; i < tt.size(); ++i)
6358 for (std::size_t c = 0; c < tt[i].size(); ++c) {
6359 if (tt[i][c].empty())
continue;
6362 :
"Station" + std::to_string(i);
6367 e[
"t"] = vector_json(tt[i][c]);
6368 e[
"F"] = vector_json(ff[i][c]);
6373 emit_analysis<double>(key, p, std::string());
6377 std::printf(
"%-16s %-14s %14s %14s\n",
"Station",
"JobClass",
"Time",
"F(t)");
6378 for (std::size_t i = 0; i < tt.size(); ++i)
6379 for (std::size_t c = 0; c < tt[i].size(); ++c)
6380 for (std::size_t j = 0; j < tt[i][c].size(); ++j)
6381 std::printf(
"%-16s %-14s %14.8g %14.10g\n",
6384 tt[i][c][j], ff[i][c][j]);
6401int solve_model_ldes_sample(
const std::string& file,
const Knobs& k) {
6406 std::vector<std::string> extra;
6407 extra.push_back(
"--trajectory");
6409 if (r.
t.empty() || r.
QNt.empty())
6411 "SolverLDES -a sample produced no trajectory over [0," +
6412 line::ldes::detail::shortest(o.
t1) +
"]");
6414 const std::size_t M = r.
QNt.size(), K = r.
class_names.size(), n = r.
t.size();
6415 if (g_json_output) {
6417 p[
"type"] =
"SamplePath";
6419 p[
"scope"] =
"(system)";
6421 p[
"t"] = vector_json(r.
t);
6423 for (std::size_t j = 0; j < n; ++j) {
6425 for (std::size_t i = 0; i < M; ++i)
6426 for (std::size_t c = 0; c < K; ++c)
6427 row.push_back(c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
6434 for (std::size_t i = 0; i < M; ++i)
6435 for (std::size_t c = 0; c < K; ++c)
6438 p[
"columns"] = cols;
6443 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6444 p[it.key()] = it.value();
6445 emit_analysis<double>(
"sample", p, r.
method);
6449 std::printf(
"%14s %s\n",
"Time",
"SysState (station-major, per class)");
6450 for (std::size_t j = 0; j < n; ++j) {
6451 std::printf(
"%14.8g ", r.
t[j]);
6452 for (std::size_t i = 0; i < M; ++i)
6453 for (std::size_t c = 0; c < K; ++c)
6454 std::printf(
" %g", c < r.
QNt[i].size() && j < r.
QNt[i][c].rows()
6476int solve_model_ldes_reward(
const std::string& file,
const Knobs& k) {
6481 "-s ldes -a reward needs a reward declared on the model (set_reward(name, fn), the "
6482 "`rewards` block of model.json); there is nothing to average");
6485 std::vector<std::string> extra;
6486 extra.push_back(
"--export-histogram");
6490 "SolverLDES -a reward needs the joint-state residence-time histogram the engine "
6491 "exports under --export-histogram and the run returned none");
6498 "SolverLDES -a reward: the state histogram carries no residence time, so no state "
6499 "distribution can be formed from it");
6502 std::vector<double> E(sn.
reward.size(), 0.0);
6503 std::vector<std::string> names(sn.
reward.size());
6504 for (std::size_t l = 0; l < sn.
reward.size(); ++l) {
6505 names[l] = sn.
reward[l].name;
6506 for (std::size_t s = 0; s < ns; ++s) {
6507 std::vector<double> row(w);
6512 E[l] += (t / total) * sn.
reward[l].fn(row);
6516 if (g_json_output) {
6518 p[
"type"] =
"AvgReward";
6520 for (std::size_t l = 0; l < names.size(); ++l) nm.push_back(names[l]);
6522 p[
"E"] = vector_json(E);
6528 for (line::reg::Json::const_iterator it = env.begin(); it != env.end(); ++it)
6529 p[it.key()] = it.value();
6532 emit_analysis<double>(
"reward", p, std::string());
6536 std::printf(
"%-28s %16s\n",
"Reward",
"E[r]");
6537 for (std::size_t l = 0; l < E.size(); ++l)
6538 std::printf(
"%-28s %16.10g\n", names[l].c_str(), E[l]);
6561int solve_model_ldes_prob(
const std::string& file,
const Knobs& k) {
6567 if (!k.state.empty()) {
6568 std::size_t ist = 0;
6569 if (k.node && k.node <= sn.
nodes.size()) ist = sn.
nodes[k.node - 1].station;
6572 " is not a station; the LDES state histogram records station "
6573 "queue lengths only");
6574 if (k.state.size() != R)
6575 throw line::InputError(
"-s ldes -a prob takes --state as one job count per class (" +
6576 std::to_string(R) +
"), got " + std::to_string(k.state.size()));
6577 for (std::size_t r = 0; r < R; ++r) nir(ist - 1, r) =
static_cast<double>(k.state[r]);
6581 std::vector<std::string> extra;
6582 extra.push_back(
"--export-histogram");
6588 "SolverLDES -a prob needs the joint-state residence-time histogram the engine exports "
6589 "under --export-histogram and the run returned none");
6590 if (space.
cols() < M * R)
6592 std::to_string(space.
cols()) +
" columns, fewer than the " +
6593 std::to_string(M * R) +
" of " + std::to_string(M) +
6594 " stations by " + std::to_string(R) +
" classes");
6596 std::vector<double> dwell(space.
rows(), 0.0);
6598 for (std::size_t s = 0; s < space.
rows() && s < tm.
rows(); ++s)
6599 for (std::size_t c = 0; c < tm.
cols(); ++c) dwell[s] += tm(s, c);
6600 for (
double d : dwell) total += d;
6603 "SolverLDES -a prob: the state histogram carries no residence time, so no state "
6604 "probability can be formed from it");
6607 auto matches = [&](std::size_t s, std::size_t i) {
6608 for (std::size_t r = 0; r < R; ++r)
6609 if (!(std::abs(space(s, i * R + r) - nir(i, r)) < 1e-9))
return false;
6612 std::vector<double> prob(M, 0.0);
6613 std::vector<bool> seen(M,
false);
6615 bool sys_seen =
false;
6616 for (std::size_t s = 0; s < space.
rows(); ++s) {
6618 for (std::size_t i = 0; i < M; ++i) {
6620 if (matches(s, i)) {
6621 prob[i] += dwell[s];
6632 for (
double& p : prob) p /= total;
6635 if (g_json_output) {
6637 p[
"type"] =
"ProbAggr";
6639 p[
"ProbSys"] = psys;
6640 p[
"ProbSysAggr"] = psys;
6641 p[
"SysStateSeen"] = sys_seen;
6642 p[
"states"] = space.
rows();
6643 line::reg::Json st = line::reg::Json::array(), pa = line::reg::Json::array(),
6644 sv = line::reg::Json::array();
6645 for (std::size_t i = 0; i < M; ++i) {
6647 pa.push_back(prob[i]);
6648 sv.push_back(
static_cast<bool>(seen[i]));
6653 p[
"StateSeen"] = sv;
6654 emit_analysis<double>(
"prob", p, res.
method);
6657 ldes_banner(res, o);
6658 std::printf(
"ProbSys = ProbSysAggr %.10g%s\n", psys, sys_seen ?
"" :
" (state never observed)");
6659 std::printf(
"%-16s %14s\n",
"Station",
"Prob=ProbAggr");
6660 for (std::size_t i = 0; i < M; ++i)
6661 std::printf(
"%-16s %14.10g%s\n", sn.
stations[i].name.c_str(), prob[i],
6662 seen[i] ?
"" :
" (state never observed)");
6672int solve_model_ldes(
const std::string& file,
const Knobs& k,
const std::string& analysis) {
6673 if (analysis ==
"avg")
return solve_model_ldes_avg(file, k);
6674 if (analysis ==
"tran")
return solve_model_ldes_tran(file, k);
6682 if (analysis ==
"cdf")
return solve_model_ldes_cdf(file, k,
"cdf",
"CdfRespT");
6683 if (analysis ==
"cdfpasst")
return solve_model_ldes_cdf(file, k,
"cdfpasst",
"CdfPassT");
6684 if (analysis ==
"trancdf")
return solve_model_ldes_cdf(file, k,
"trancdf",
"TranCdfRespT");
6685 if (analysis ==
"trancdfpasst")
6686 return solve_model_ldes_cdf(file, k,
"trancdfpasst",
"TranCdfPassT");
6687 if (analysis ==
"sample")
return solve_model_ldes_sample(file, k);
6688 if (analysis ==
"reward")
return solve_model_ldes_reward(file, k);
6689 if (analysis ==
"prob")
return solve_model_ldes_prob(file, k);
6691 "SolverLDES ports -a avg (getAvg) and its four views -a node, -a sys, -a chain and "
6692 "-a nodechain, -a tran (getTranAvg), -a cdf / cdf-passt / "
6693 "tran-cdf-respt / tran-cdf-passt (the empirical passage law, one measurement under the "
6694 "four names the reference gives it), -a sample (sampleSys), -a reward "
6695 "(getAvgReward) and -a prob (getProb, getProbAggr, getProbSys, getProbSysAggr); got '" +
6699std::string auto_getter_of_analysis(
const std::string& analysis) {
6700 if (analysis ==
"prob")
return "getProbSysAggr";
6701 if (analysis ==
"marg")
return "getProbMarg";
6702 if (analysis ==
"sysmarg")
return "getProbSysMarg";
6703 if (analysis ==
"normconst")
return "getProbNormConstAggr";
6704 if (analysis ==
"tranprob")
return "getTranProbSysAggr";
6705 if (analysis ==
"sample")
return "sampleSys";
6706 if (analysis ==
"cdf")
return "getCdfRespT";
6707 if (analysis ==
"gen")
return "getInfGen";
6708 if (analysis ==
"states")
return "getStateSpace";
6709 if (analysis ==
"reward")
return "getAvgReward";
6710 if (analysis ==
"sens")
return "getSensitivityTable";
6711 if (analysis ==
"tran")
return "getTranAvg";
6712 if (analysis ==
"internals")
return "getMAMResult";
6718 if (analysis ==
"node")
return "getAvgNodeTable";
6719 return "getAvgTable";
6727 std::vector<std::string> order;
6733std::string auto_cli_token_of_family(
const std::string& fam) {
6734 if (fam ==
"mva" || fam ==
"nc" || fam ==
"ctmc" || fam ==
"mam" || fam ==
"ag" ||
6735 fam ==
"ssa" || fam ==
"ba" || fam ==
"uq" || fam ==
"env")
6737 if (fam ==
"fld")
return "fluid";
6738 if (fam ==
"ldes") {
6744 "--method ldes names the discrete-event engine, and no engine was found beside "
6745 "this binary (common/ldes or common/ldes.jar, or $LINE_LDES_DIR); -s ssa is the "
6746 "simulator this port builds in process");
6760 if (fam ==
"lqns")
return "lqns";
6763 "--method ln names the layered solver, which takes a LayeredNetwork: pass the model "
6764 "as -i lqnx -s ln rather than as a Network");
6765 throw line::InputError(
"SolverAUTO: no engine stands behind method family '" + fam +
"'");
6776bool model_is_environment(
const std::string& file) {
6778 line::io::detail::json root;
6780 std::istringstream in(stdin_model_text());
6783 std::ifstream in(file.c_str());
6784 if (!in)
return false;
6790 if (!root.is_object())
return false;
6791 const line::io::detail::json& model = root.contains(
"model") ? root.at(
"model") : root;
6792 return model.is_object() && model.value(
"type", std::string()) ==
"Environment";
6818AutoPlan choose_auto_plan(
const std::string& file,
const std::string& analysis,
6819 const std::string& method_token) {
6823 plan.order.push_back(auto_cli_token_of_family(tok.
family));
6828 const std::string getter = auto_getter_of_analysis(analysis);
6829 if (model_is_environment(file)) {
6832 plan.order.push_back(
"env");
6833 for (std::size_t i = 0; i < ec.
skipped.size(); ++i)
6834 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
6836 if (!ec.
skipped.empty()) plan.note +=
", which this port does not build)";
6844 const std::vector<line::autosolver::AutoSolver> proposed =
6846 for (std::size_t i = 0; i < proposed.size(); ++i)
6848 for (std::size_t i = 0; i < c.
skipped.size(); ++i)
6849 plan.note += std::string(i ?
", " :
" (the ranking preferred ") +
6851 if (!c.
skipped.empty()) plan.note +=
", which this port does not build)";
6855AutoPlan choose_auto_plan_dispatch(
const std::string& arith,
const std::string& file,
6856 const std::string& analysis,
const std::string& method_token) {
6857 if (arith ==
"exact")
return choose_auto_plan<line::Rational>(file, analysis, method_token);
6858 if (arith ==
"real:16")
return choose_auto_plan<line::Real<16> >(file, analysis, method_token);
6859 if (arith ==
"real" || arith ==
"real:32")
6860 return choose_auto_plan<line::Real<32> >(file, analysis, method_token);
6861 if (arith ==
"real:64")
return choose_auto_plan<line::Real<64> >(file, analysis, method_token);
6862 if (arith ==
"real:128")
6863 return choose_auto_plan<line::Real<128> >(file, analysis, method_token);
6864 if (arith ==
"real:256")
6865 return choose_auto_plan<line::Real<256> >(file, analysis, method_token);
6866 return choose_auto_plan<double>(file, analysis, method_token);
6880double ln_sanitize(
double x) {
6881 const double r = std::round(x * 10.0);
6888 switch (l.
type[i]) {
6892 default:
return "Activity";
6908 using namespace line;
6909 const std::vector<qn::Layer<T> >& ens = solver.
layers();
6910 for (std::size_t k = 0; k < ens.size(); ++k) {
6912 std::printf(
"LAYER %zu %s nstations=%zu nclasses=%zu nchains=%zu\n", k + 1, L.
name.c_str(),
6914 for (std::size_t i = 0; i < L.
stations.size(); ++i)
6915 std::printf(
" STATION %zu %s sched=%s nservers=%g\n", i + 1, L.
stations[i].name.c_str(),
6917 for (std::size_t r = 0; r < L.
classes.size(); ++r)
6918 std::printf(
" CLASS %zu %s pop=%.17g refstat=%zu completes=%d\n", r + 1,
6921 for (std::size_t i = 0; i < L.
stations.size(); ++i)
6922 for (std::size_t r = 0; r < L.
classes.size(); ++r) {
6924 std::printf(
" RATE %s %s %.17g scv=%.17g\n", L.
stations[i].name.c_str(),
6928 for (
const auto& kv : L.
P) {
6930 for (std::size_t i = 0; i < B.
rows(); ++i)
6931 for (std::size_t j = 0; j < B.
cols(); ++j) {
6933 if (p == 0.0)
continue;
6934 std::printf(
" ROUTE %s->%s %s->%s %.17g\n",
6935 L.
classes[kv.first.first - 1].name.c_str(),
6936 L.
classes[kv.first.second - 1].name.c_str(),
6937 L.
nodes[i].name.c_str(), L.
nodes[j].name.c_str(), p);
6956 using namespace line;
6961 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
6962 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
6963 if (k.no_interlocking)
opt.interlocking =
false;
6964 if (!k.interlock_method.empty())
opt.interlock_method = k.interlock_method;
6965 if (k.interlock_maxpaths >= 0.0)
opt.interlock_maxpaths = k.interlock_maxpaths;
6966 if (!k.interlock_refpath_scope.empty())
opt.interlock_refpath_scope = k.interlock_refpath_scope;
6967 if (!k.layer_solver.empty())
opt.layer_solver = k.layer_solver;
6968 if (!k.method.empty())
opt.method = k.method;
6969 if (k.samples)
opt.layer_ssa.samples = k.samples;
6970 if (k.seed)
opt.layer_ssa.seed = k.seed;
6971 if (k.t1 >= 0.0)
opt.timespan_end = k.t1;
6972 if (k.tran_points)
opt.tran_points = k.tran_points;
6973 if (!k.ln_transient.empty())
opt.ln_transient = k.ln_transient;
6974 if (!k.ln_transient_channels.empty())
opt.ln_transient_channels = k.ln_transient_channels;
6979inline const char* ln_layer_engine_name(
const std::string& layer_solver) {
6980 if (layer_solver ==
"fluid")
return "Fluid";
6981 if (layer_solver ==
"nc")
return "NC";
6982 if (layer_solver ==
"ssa")
return "SSA";
6994int run_ln_tran(
const std::string& file,
const std::string& output,
const Knobs& k) {
6995 using namespace line;
7000 const std::vector<qn::Layer<T>>& layers = solver.
layers();
7002 if (output ==
"json") {
7006 j[
"mode"] =
tr.mode;
7007 j[
"iterations"] =
tr.iterations;
7008 j[
"gap"] = ln_sanitize(
tr.gap);
7010 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
7012 le[
"layer"] = layers[e].name;
7014 for (
double t :
tr.layers[e].t) tt.push_back(ln_sanitize(t));
7017 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
7018 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
7020 s[
"station"] = layers[e].stations[i].name;
7021 s[
"class"] = layers[e].classes[r].name;
7022 auto arr = [&](
const std::vector<double>& v) {
7024 for (
double x : v) a.push_back(ln_sanitize(x));
7027 s[
"QLen"] = arr(
tr.layers[e].QN[i][r]);
7028 s[
"Util"] = arr(
tr.layers[e].UN[i][r]);
7029 s[
"Tput"] = arr(
tr.layers[e].TN[i][r]);
7030 series.push_back(s);
7032 le[
"series"] = series;
7036 emit_document(dump_document(j, 1));
7040 std::printf(
"SolverLN(Solver%s) getTranAvg arith=%s mode=%s layers=%zu iterations=%ld gap=%.3e\n",
7042 tr.layers.size(),
tr.iterations,
tr.gap);
7043 for (std::size_t e = 0; e <
tr.layers.size(); ++e) {
7044 const std::vector<double>& t =
tr.layers[e].t;
7045 if (t.empty())
continue;
7046 std::printf(
"\nLayer %s (%zu points on [%.6g, %.6g])\n", layers[e].name.c_str(), t.size(),
7047 t.front(), t.back());
7048 std::printf(
"%-30s %-24s %12s %12s %12s %12s\n",
"Station",
"JobClass",
"QLen(0)",
7049 "QLen(end)",
"Util(end)",
"Tput(end)");
7050 for (std::size_t i = 0; i <
tr.layers[e].QN.size(); ++i)
7051 for (std::size_t r = 0; r <
tr.layers[e].QN[i].size(); ++r) {
7052 const std::vector<double>& q =
tr.layers[e].QN[i][r];
7053 if (q.empty())
continue;
7054 std::printf(
"%-30s %-24s %12.6g %12.6g %12.6g %12.6g\n",
7055 layers[e].stations[i].name.c_str(), layers[e].classes[r].name.c_str(),
7056 ln_sanitize(q.front()), ln_sanitize(q.back()),
7057 ln_sanitize(
tr.layers[e].UN[i][r].back()),
7058 ln_sanitize(
tr.layers[e].TN[i][r].back()));
7066int run_ln_sens(
const std::string& file,
const std::string& output,
const Knobs& k) {
7067 using namespace line;
7072 if (!k.sens_method.empty()) so.
method = k.sens_method;
7073 if (!k.sens_scheme.empty()) so.
scheme = k.sens_scheme;
7074 if (k.sens_step > 0.0) so.
step = k.sens_step;
7077 if (output ==
"json") {
7081 j[
"method"] = tbl.
method;
7083 for (
const auto& r : tbl.
rows) {
7085 o[
"Layer"] = r.layer;
7086 o[
"Station"] = r.station;
7087 o[
"JobClass"] = r.jobclass;
7095 emit_document(dump_document(j, 1));
7099 std::printf(
"SolverLN(Solver%s) getSensitivityTable arith=%s method=%s rows=%zu\n",
7102 std::printf(
"%-28s %-28s %-20s %14s %14s %14s %14s\n",
"Layer",
"Station",
"JobClass",
7103 "dTput_dRate",
"dRespT_dRate",
"dQLen_dRate",
"dUtil_dRate");
7104 for (
const auto& r : tbl.
rows)
7105 std::printf(
"%-28s %-28s %-20s %14.6g %14.6g %14.6g %14.6g\n", r.layer.c_str(),
7106 r.station.c_str(), r.jobclass.c_str(),
7116int run_ln_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
7117 using namespace line;
7123 opt.method =
"moment3";
7128 if (output ==
"json") {
7133 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
7135 o[
"entry"] = names.names[model.
eshift + e];
7136 reg::Json tt = reg::Json::array(), ff = reg::Json::array();
7137 for (std::size_t p = 0; p < cdf[e].t.size(); ++p) {
7138 tt.push_back(ln_sanitize(cdf[e].t[p]));
7139 ff.push_back(ln_sanitize(cdf[e].cdf[p]));
7145 j[
"entries"] = rows;
7146 emit_document(dump_document(j, 1));
7150 std::printf(
"SolverLN(Solver%s) getCdfRespT arith=%s method=moment3 entries=%zu\n",
7152 for (std::size_t e = 1; e <= model.
nentries && e < cdf.size(); ++e) {
7153 if (cdf[e].t.empty()) {
7154 std::printf(
"%-40s (no distribution: the entry has no fitted term)\n",
7155 names.names[model.
eshift + e].c_str());
7159 auto quantile = [&](
double p) {
7160 for (std::size_t i = 0; i < cdf[e].cdf.size(); ++i)
7161 if (cdf[e].cdf[i] >= p)
return cdf[e].t[i];
7162 return cdf[e].t.back();
7164 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g points=%zu\n",
7165 names.names[model.
eshift + e].c_str(), ln_sanitize(quantile(0.25)),
7166 ln_sanitize(quantile(0.50)), ln_sanitize(quantile(0.75)),
7167 ln_sanitize(quantile(0.95)), cdf[e].t.size());
7173int run_ln(
const std::string& file,
const std::string& output,
const Knobs& k) {
7174 using namespace line;
7178 const int repeat = k.repeat > 0 ? k.repeat : 1;
7180 double best = 1e300;
7182 std::size_t nlayers = 0;
7183 for (
int rep = 0; rep < repeat; ++rep) {
7184 const auto t0 = std::chrono::steady_clock::now();
7188 if (output ==
"layers") {
7189 ln_dump_layers(solver);
7192 const auto t1 = std::chrono::steady_clock::now();
7193 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
7199 if (output ==
"json") {
7203 j[
"layers"] = nlayers;
7206 j[
"seconds"] = best;
7208 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7210 r[
"node"] = names.names[i];
7211 r[
"type"] = ln_element_kind(names, i);
7212 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
7214 else if (sol.
is_bound) r[key] = 0.0;
7215 else r[key] =
nullptr;
7225 emit_document(dump_document(j, 1));
7233 "SolverLN(Solver%s) arith=%s type=%s layers=%zu iterations=%d converged=%d time=%.4fs\n",
7237 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7238 "RespT",
"ResidT",
"Tput");
7239 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7252 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
7253 if (!d[i] && sol.
is_bound) std::snprintf(buf, 24,
"%12.6g", 0.0);
7254 else if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
7257 char q[24], u[24], rr[24], w[24], t[24];
7263 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
7264 ln_element_kind(names, i), q, u, rr, w, t);
7312int run_ln_ldes_cdf(
const std::string& file,
const std::string& output,
const Knobs& k) {
7313 using namespace line;
7316 if (k.samples) o.
samples = k.samples;
7317 if (k.seed) o.
seed =
static_cast<long>(k.seed);
7323 const std::vector<ldes::engine::LnEntryCdf> cdf =
7326 for (std::size_t e = 0; e < model.
nentries; ++e) {
7331 if (output ==
"json") {
7334 j[
"engine"] =
"native-ln";
7336 for (std::size_t e = 0; e < model.
nentries; ++e) {
7339 reg::Json at = reg::Json::array(), af = reg::Json::array();
7340 for (std::size_t i = 0; i < tt[e].size(); ++i) {
7341 at.push_back(tt[e][i]);
7342 af.push_back(ff[e][i]);
7346 ob[
"observations"] =
static_cast<double>(
7350 j[
"entries"] = rows;
7351 emit_document(dump_document(j, 1));
7355 std::printf(
"SolverLDES(native LN engine) getCdfRespT entries=%zu\n", model.
nentries);
7356 for (std::size_t e = 0; e < model.
nentries; ++e) {
7357 if (tt[e].empty()) {
7358 std::printf(
"%-40s (no observation)\n", model.
names[model.
eshift + e + 1].c_str());
7363 const std::vector<double>& t;
7364 const std::vector<double>& f;
7365 double operator()(
double p)
const {
7366 for (std::size_t i = 0; i < f.size(); ++i)
7367 if (f[i] >= p)
return t[i];
7370 } q = {tt[e], ff[e]};
7371 std::printf(
"%-40s p25=%12.6g p50=%12.6g p75=%12.6g p95=%12.6g n=%zu\n",
7372 model.
names[model.
eshift + e + 1].c_str(), q(0.25), q(0.50), q(0.75),
7378int run_ln_ldes(
const std::string& file,
const std::string& output,
const Knobs& k) {
7379 using namespace line;
7386 if (k.samples) o.
samples = k.samples;
7387 if (k.seed) o.
seed =
static_cast<long>(k.seed);
7394 const int repeat = k.repeat > 0 ? k.repeat : 1;
7395 double best = 1e300;
7397 for (
int rep = 0; rep < repeat; ++rep) {
7398 const auto t0 = std::chrono::steady_clock::now();
7400 const auto t1 = std::chrono::steady_clock::now();
7401 best = std::min(best, std::chrono::duration<double>(t1 - t0).count());
7409 const auto def = [&](std::size_t i, Col c) {
7413 if (output ==
"json") {
7416 j[
"arith"] =
"double";
7417 j[
"solver"] =
"ldes";
7422 j[
"engine"] =
"native-ln";
7427 j[
"seconds"] = best;
7429 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7431 row[
"node"] = model.
names[i];
7432 row[
"type"] = ln_element_kind(model, i);
7433 auto put = [&](
const char* key,
double v,
bool defined) {
7434 if (defined) row[key] = v;
7435 else row[key] =
nullptr;
7437 put(
"QLen", r.
QLN(i, 0), def(i, Col::QLen));
7438 put(
"Util", r.
ULN(i, 0), def(i, Col::Util));
7439 put(
"RespT", r.
RLN(i, 0), def(i, Col::RespT));
7440 put(
"ResidT", r.
WLN(i, 0), def(i, Col::ResidT));
7441 put(
"Tput", r.
TLN(i, 0), def(i, Col::Tput));
7442 rows.push_back(row);
7445 emit_document(dump_document(j, 1));
7449 std::printf(
"SolverLDES(native LN engine) arith=double type=%s samples=%zu seed=%ld "
7450 "simtime=%.6g completions=%lld time=%.4fs\n",
7453 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7454 "RespT",
"ResidT",
"Tput");
7455 for (std::size_t i = 1; i <= model.
nidx; ++i) {
7457 auto fmt = [&](
double v,
bool defined,
char* buf) {
7458 if (!defined) std::snprintf(buf, 24,
"%12s",
"NaN");
7459 else std::snprintf(buf, 24,
"%12.6g", v);
7461 char q[24], u[24], rr[24], w[24], t[24];
7462 fmt(r.
QLN(i, 0), def(i, Col::QLen), q);
7463 fmt(r.
ULN(i, 0), def(i, Col::Util), u);
7464 fmt(r.
RLN(i, 0), def(i, Col::RespT), rr);
7465 fmt(r.
WLN(i, 0), def(i, Col::ResidT), w);
7466 fmt(r.
TLN(i, 0), def(i, Col::Tput), t);
7467 std::printf(
"%-62s %-10s %s %s %s %s %s\n", model.
names[i].c_str(),
7468 ln_element_kind(model, i), q, u, rr, w, t);
7485int run_lqns(
const std::string& file,
const std::string& output,
const Knobs& k) {
7486 using namespace line;
7490 if (!k.method.empty())
opt.method = k.method;
7491 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
7492 if (k.samples)
opt.samples =
static_cast<double>(k.samples);
7493 opt.verbose = k.verbose;
7495 opt.remote = k.remote;
7496 if (!k.remote_url.empty())
opt.remote_url = k.remote_url;
7497 opt.timeout_seconds = k.timeout_seconds;
7504 if (output ==
"json") {
7509 j[
"method"] =
opt.method;
7511 j[
"seconds"] = solver.runtime();
7513 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
7515 r[
"node"] = names.names[i];
7516 r[
"type"] = ln_element_kind(names, i);
7517 auto put = [&](
const char* key,
const std::vector<T>& v,
const std::vector<bool>& d) {
7519 else r[key] =
nullptr;
7529 emit_document(dump_document(j, 1));
7533 std::printf(
"SolverLQNS(%s) arith=%s type=%s iterations=%d time=%.4fs\n",
7537 std::printf(
"%-62s %-10s %12s %12s %12s %12s %12s\n",
"Node",
"NodeType",
"QLen",
"Util",
7538 "RespT",
"ResidT",
"Tput");
7539 for (std::size_t i = 1; i <=
sn.nidx; ++i) {
7541 auto fmt = [&](
const std::vector<T>& v,
const std::vector<bool>& d,
char* buf) {
7542 if (!d[i]) std::snprintf(buf, 24,
"%12s",
"NaN");
7544 std::snprintf(buf, 24,
"%12.6g",
7547 char q[24], u[24], rr[24], w[24], t[24];
7553 std::printf(
"%-62s %-10s %s %s %s %s %s\n", names.names[i].c_str(),
7554 ln_element_kind(names, i), q, u, rr, w, t);
7580std::string upper_tag(
const std::string& s) {
7581 std::string out = s;
7582 for (std::size_t i = 0; i < out.size(); ++i)
7583 out[i] =
static_cast<char>(std::toupper(
static_cast<unsigned char>(out[i])));
7587int solve_lqn_dispatch(
const std::string& arith,
const std::string& solver,
7588 const std::string& analysis,
const std::string& output,
7589 const std::string& file,
const Knobs& k) {
7592 "a layered model is read from a file: pass -f <model.lqnx> or -f <model.json> (neither "
7593 "layered reader has a stdin form)");
7594 if (analysis !=
"avg" && analysis !=
"tran" && analysis !=
"sens" && analysis !=
"cdf")
7596 "the layered path ports -a avg (getAvgTable), -a tran (getTranAvg), -a sens "
7597 "(getSensitivityTable) and -a cdf (getCdfRespT); got '" + analysis +
"'");
7598 const std::string s = solver.empty() ?
"auto" : solver;
7599 if (s !=
"auto" && s !=
"ln" && s !=
"ln.mva" && s !=
"ln.comom" && s !=
"lqns" &&
7602 "the layered path takes -s ln, ln.mva, ln.comom, ldes, lqns or auto (got '" + s +
7603 "'); a Network solver cannot be applied to a LayeredNetwork directly");
7612 std::string engine = s;
7616 if (s ==
"auto" && kk.layer_solver.empty()) {
7617 std::string getter =
"getAvgTable";
7618 if (analysis ==
"tran") getter =
"getTranAvg";
7619 else if (analysis ==
"cdf") getter =
"getCdfRespT";
7620 else if (analysis ==
"sens") getter =
"getSensitivityTable";
7624 bool has_cache_task =
false;
7625 for (std::size_t i = 0; i < probe.
iscache.size(); ++i)
7626 if (probe.
iscache[i]) has_cache_task =
true;
7630 if (token ==
"lqns") engine =
"lqns";
7631 else if (token ==
"ln.comom") kk.layer_solver =
"nc";
7632 else if (token ==
"ln.fld") kk.layer_solver =
"fluid";
7633 else kk.layer_solver =
"mva";
7635 for (std::size_t i = 0; i < lc.
skipped.size(); ++i)
7636 note += std::string(i ?
", " :
" (the ranking preferred ") +
7638 if (!lc.
skipped.empty()) note +=
", which is not available here)";
7639 std::printf(
"SolverAUTO selected %s%s\n", token.c_str(), note.c_str());
7647 if (engine ==
"lqns") {
7648 if (analysis !=
"avg")
7650 "SolverLQNS reports the mean table its binary computes; it has no transient, no "
7651 "sensitivity and no response-time distribution here, so it takes -a avg (got '" +
7653 if (!kk.layer_solver.empty())
7655 "--layer-solver names the engine SolverLN runs on each layer; -s lqns solves no "
7656 "layers, it hands the whole model to the lqns binary");
7657 if (output ==
"layers")
7659 "-o layers dumps the stations and routing SolverLN BUILT from the model; lqns "
7660 "builds its own submodels inside another process and this port never sees them");
7661 if (kk.iter_tol >= 0.0 || kk.iter_max > 0)
7663 "--iter_tol and --iter_max are SolverLN's layer-iteration knobs; lqns runs its own "
7664 "iteration and takes neither (its --iteration-limit is unreliable as of 6.2.27, "
7665 "which is why the reference stopped passing it)");
7668 "--seed sets the stream of a simulator this port drives; lqsim seeds itself and "
7669 "the wrapper passes no seed, exactly as the reference does not");
7672 "--repeat times a solve by re-running it; re-running lqsim would report a "
7673 "different answer under the same banner");
7674 if (kk.no_interlocking || kk.interlock_knobs_given() || !kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
7675 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0)
7677 "--no-interlocking, --interlock-*, --ln-transient*, and --sens-* are SolverLN options; -s lqns "
7678 "has none of them");
7679 if (arith !=
"double")
7681 "SolverLQNS reads a result file another program wrote in decimal double "
7682 "precision; there is no higher precision to carry, so rerun with --arith double "
7683 "(got '" + arith +
"')");
7690 if (kk.has_cutoff())
7692 "--cutoff bounds the open population of a CTMC state space and applies to -s "
7693 "ctmc; the layered path enumerates no states");
7696 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7697 "reports every LQN element");
7698 return run_lqns<double>(file, output, kk);
7700 if (k.keep || k.verbose || k.remote || !k.remote_url.empty() || k.timeout_seconds)
7702 "--keep, --verbose, --remote, --remote-url and --timeout describe the child process "
7703 "of an external solver and apply to -s lqns only");
7710 if (engine ==
"ldes") {
7711 if (analysis !=
"avg" && analysis !=
"cdf")
7713 "the native LN engine measures a sample path: it takes -a avg for the mean table "
7714 "and -a cdf for the per-entry response time distribution, and has no transient "
7715 "and no sensitivity here (got '" + analysis +
"')");
7716 if (arith !=
"double")
7718 "the native LN engine accumulates its estimators in double, so there is no higher "
7719 "precision to carry; rerun with --arith double (got '" + arith +
"')");
7720 if (output ==
"layers")
7722 "-o layers dumps the stations and routing SolverLN BUILT from the model; -s ldes "
7723 "simulates the layered semantics directly and builds no submodels");
7724 if (!kk.layer_solver.empty())
7726 "--layer-solver names the engine SolverLN runs on each layer; -s ldes solves no "
7727 "layers, it simulates entries, activities and calls directly");
7728 if (kk.iter_tol >= 0.0 || kk.iter_max > 0 || kk.no_interlocking || kk.interlock_knobs_given())
7730 "--iter_tol, --iter_max, --no-interlocking and --interlock-* are SolverLN's layer-iteration "
7731 "knobs; a simulated sample path converges by run length, which is --samples");
7732 if (!kk.ln_transient.empty() || !kk.ln_transient_channels.empty() ||
7733 !kk.sens_method.empty() || !kk.sens_scheme.empty() || kk.sens_step > 0.0)
7735 "--ln-transient* and --sens-* are SolverLN options; -s ldes has none of them");
7736 if (!kk.method.empty() && kk.method !=
"default")
7738 "--method on the layered path names the LN UPDATE (default, moment3, mw.*); "
7739 "-s ldes performs no update, it simulates the model (got '" + kk.method +
"')");
7745 if (!kk.ldes_tranfilter.empty() || kk.ldes_warmupfrac >= 0.0 ||
7746 !kk.ldes_cimethod.empty() || kk.ldes_cnvgon || kk.ldes_cnvgtol > 0.0 ||
7747 kk.ldes_slotted || kk.ldes_slotlength > 0.0 || kk.ldes_replications > 0 ||
7748 kk.ldes_numthreads > 0 || kk.ldes_maxtime > 0.0 || !kk.ldes_initsol.empty() ||
7749 !kk.ldes_rest_url.empty())
7751 "the --ldes-* flags configure the SUBPROCESS engine that answers -s ldes on a "
7752 "Network (warmup filter, CI estimator, slot lattice, replications, warm-start "
7753 "placement); the native LN engine behind -i lqnx -s ldes reads --samples and "
7758 if (kk.has_cutoff())
7760 "--cutoff bounds the open population of a CTMC state space and applies to -s "
7761 "ctmc; the layered path enumerates no states");
7764 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7765 "reports every LQN element");
7768 "--tspan sets the horizon of a transient analysis; the native LN engine runs to "
7769 "a completion budget, which is --samples");
7772 "--tol is not an LDES option; the run length is set with --samples");
7773 if (analysis ==
"cdf")
return run_ln_ldes_cdf(file, output, kk);
7774 return run_ln_ldes(file, output, kk);
7778 if (s ==
"ln.comom") {
7779 if (!kk.layer_solver.empty() && kk.layer_solver !=
"nc")
7780 throw line::InputError(
"-s ln.comom already selects NC layers, but --layer-solver says '" +
7781 kk.layer_solver +
"'");
7782 kk.layer_solver =
"nc";
7783 }
else if (s ==
"ln.mva") {
7784 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva")
7785 throw line::InputError(
"-s ln.mva already selects MVA layers, but --layer-solver says '" +
7786 kk.layer_solver +
"'");
7787 kk.layer_solver =
"mva";
7792 if (!kk.layer_solver.empty() && kk.layer_solver !=
"mva" && kk.layer_solver !=
"nc" &&
7793 kk.layer_solver !=
"fluid" && kk.layer_solver !=
"ssa")
7794 throw line::InputError(
"--layer-solver takes mva, nc, fluid or ssa (got '" +
7795 kk.layer_solver +
"')");
7797 if ((k.samples || k.seed) && kk.layer_solver !=
"ssa")
7799 "--samples and --seed set the run length and the stream of a SIMULATED layer; the "
7800 "layered path draws no random numbers unless --layer-solver ssa is in force");
7803 "--cutoff bounds the open population of a CTMC state space and applies to -s ctmc; the "
7804 "layered path enumerates no states");
7805 if (k.t1 >= 0.0 && analysis !=
"tran")
7807 "--tspan sets the horizon of a transient analysis and applies to the layered path "
7808 "only with -a tran");
7809 if (analysis ==
"tran" && !(k.t1 >= 0.0))
7811 "-a tran integrates each layer's drift and needs a horizon: pass --tspan <t0>:<t1>");
7814 "--node selects the stateful node a CTMC query is labelled by; the layered path "
7815 "reports every LQN element");
7818 "--tol is not a SolverLN option (LnOptions carries iter_tol and iter_max); "
7823 if (!k.method.empty() && k.method !=
"default" && k.method !=
"moment3" &&
7824 k.method !=
"mw.upper" && k.method !=
"mw.lower")
7826 "--method on the layered path takes default, moment3, mw.upper or mw.lower "
7827 "(got '" + k.method +
"'); the per-layer engine is chosen with --layer-solver");
7828 if ((k.method ==
"mw.upper" || k.method ==
"mw.lower") && analysis !=
"avg")
7830 "--method mw.* reports a throughput and utilization BOUND and solves no layer, so "
7831 "it has no transient, no sensitivity and no response-time law; use -a avg");
7832 if (!k.sens_method.empty() && analysis !=
"sens")
7834 if (!k.ln_transient.empty() && analysis !=
"tran")
7837 if (analysis ==
"tran") {
7838 if (arith !=
"double")
7840 "the layered transient integrates each layer's drift with LSODA, which is double "
7841 "precision by construction; rerun with --arith double (got '" + arith +
"')");
7842 return run_ln_tran<double>(file, output, kk);
7844 if (analysis ==
"cdf") {
7845 if (arith !=
"double")
7847 "-a cdf fits an APH to a fluid passage time, integrated by LSODA in double "
7848 "precision; rerun with --arith double (got '" + arith +
"')");
7849 return run_ln_cdf<double>(file, output, kk);
7851 if (analysis ==
"sens") {
7852 if (arith ==
"double")
return run_ln_sens<double>(file, output, kk);
7853 if (arith ==
"exact")
return run_ln_sens<line::Rational>(file, output, kk);
7854 if (arith ==
"real:16")
return run_ln_sens<line::Real<16> >(file, output, kk);
7855 if (arith ==
"real" || arith ==
"real:32")
7856 return run_ln_sens<line::Real<32> >(file, output, kk);
7857 if (arith ==
"real:64")
return run_ln_sens<line::Real<64> >(file, output, kk);
7858 if (arith ==
"real:128")
return run_ln_sens<line::Real<128> >(file, output, kk);
7859 if (arith ==
"real:256")
return run_ln_sens<line::Real<256> >(file, output, kk);
7860 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7863 if (arith ==
"double")
return run_ln<double>(file, output, kk);
7864 if (arith ==
"exact")
return run_ln<line::Rational>(file, output, kk);
7866 if (arith ==
"real:16")
return run_ln<line::Real<16> >(file, output, kk);
7867 if (arith ==
"real" || arith ==
"real:32")
return run_ln<line::Real<32> >(file, output, kk);
7868 if (arith ==
"real:64")
return run_ln<line::Real<64> >(file, output, kk);
7869 if (arith ==
"real:128")
return run_ln<line::Real<128> >(file, output, kk);
7870 if (arith ==
"real:256")
return run_ln<line::Real<256> >(file, output, kk);
7871 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
7919 const std::size_t M =
sn.nstations, K =
sn.nclasses;
7928 for (std::size_t j = 0; j < M; ++j)
7929 if (
sn.stations[j].name == a.
station_names[i]) { st_of[i] = j + 1;
break; }
7930 std::vector<std::size_t> cl_of(a.
class_names.size(), 0);
7931 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7932 for (std::size_t j = 0; j < K; ++j)
7933 if (
sn.classes[j].name == a.
class_names[c]) { cl_of[c] = j + 1;
break; }
7934 for (
int m = 0; m < 6; ++m) {
7937 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7938 if (st_of[i] && cl_of[c])
7939 (*dst[m])(st_of[i] - 1, cl_of[c] - 1) = ldes_at(*src[m], i, c);
7940 for (std::size_t f = 0; f < F; ++f)
7941 for (std::size_t c = 0; c < a.
class_names.size(); ++c)
7942 if (cl_of[c]) (*dst[m])(M + f, cl_of[c] - 1) = ldes_at(*fcr[m], f, c);
7945 for (std::size_t i = 0; i < M; ++i)
7946 for (std::size_t c = 0; c < K; ++c) RNs(i, c) = r.
RN(i, c);
7948 for (std::size_t i = 0; i < M; ++i)
7949 for (std::size_t c = 0; c < K; ++c) r.
WN(i, c) = WNs(i, c);
7950 for (std::size_t c = 0; c < K; ++c) {
7951 r.
CN.push_back(ldes_at(a.
CN, 0, c));
7952 r.
XN.push_back(ldes_at(a.
XN, 0, c));
7984 const std::string& s, std::string& banner,
7985 std::string* suffix =
nullptr,
7986 const std::string* file =
nullptr) {
7988 if (s ==
"jmt" || s ==
"ldes") {
7992 if constexpr (std::is_same_v<T, double>) {
7995 if (!k.method.empty() && k.method !=
"default") o.
method = k.method;
7996 if (k.samples > 0) o.
samples =
static_cast<double>(k.samples);
7997 if (k.seed != 0) o.
seed =
static_cast<long>(k.seed);
8003 banner =
"SolverJMT";
8008 std::snprintf(buf,
sizeof(buf),
" samples=%g seed=%ld", o.
samples, o.
seed);
8014 ldes_run(file ? *file : std::string(), o, std::vector<std::string>());
8015 r = avg_result_from_ldes(
sn, a);
8016 banner =
"SolverLDES";
8019 std::snprintf(buf,
sizeof(buf),
" engine=%s samples=%zu seed=%ld",
8027 }
else if (s ==
"ssa" || s ==
"fluid") {
8034 if constexpr (std::is_same_v<T, double>) {
8037 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8038 if (k.samples)
opt.samples = k.samples;
8039 if (k.seed)
opt.seed = k.seed;
8044 std::vector<line::ssa::SsaCacheRatio>
cache;
8057 banner =
"SolverSSA";
8064 std::snprintf(buf,
sizeof(buf),
" samples=%zu seed=%lu time=%.6g", a.
samples,
8082 banner =
"SolverFluid";
8106 const bool has_ref = !refreshed.
nodes.empty();
8107 r = avg_result_from_sim<T>(has_ref ? refreshed :
sn, a.
QN, a.
UN, a.
RN, a.
TN, a.
CN,
8112 banner =
"SolverFluid";
8115 std::snprintf(buf,
sizeof(buf),
" iters=%zu", a.
iters);
8122 }
else if (s ==
"nc") {
8124 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8125 if (k.tol >= 0.0)
opt.tol = k.tol;
8126 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
8127 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8128 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
8137 if constexpr (std::is_same_v<T, double>) {
8147 banner =
"SolverNC";
8148 }
else if (s ==
"mam") {
8150 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8151 if (k.tol >= 0.0)
opt.tol = k.tol;
8152 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8154 banner =
"SolverMAM";
8155 }
else if (s ==
"ag") {
8157 apply_ag_knobs(k,
opt);
8159 banner =
"SolverAG";
8160 }
else if (s ==
"ba") {
8162 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8164 banner =
"SolverBA";
8165 }
else if (s ==
"ctmc") {
8167 if (!k.method.empty())
opt.method = k.method;
8168 if (k.cutoff >= 0.0)
opt.cutoff = k.cutoff;
8169 opt.cutoff_mat = k.cutoff_mat;
8170 opt.force = k.force;
8173 banner =
"SolverCTMC";
8176 if (!k.method.empty() && k.method !=
"default")
opt.method = k.method;
8177 if (k.tol >= 0.0)
opt.tol = k.tol;
8178 if (k.iter_tol >= 0.0)
opt.iter_tol = k.iter_tol;
8179 if (k.iter_max >= 0)
opt.iter_max = k.iter_max;
8185 if (!k.multiserver.empty())
opt.multiserver = k.multiserver;
8186 if (!k.fork_join.empty())
opt.fork_join = k.fork_join;
8189 if constexpr (std::is_same_v<T, double>) {
8201 banner =
"SolverMVA";
8212int solve_model_node(
const std::string& file,
const Knobs& k,
const std::string& s) {
8215 std::string banner, suffix;
8218 const std::size_t I =
sn.nodes.size(), R =
sn.nclasses;
8219 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8220 const line::Matrix<T>&QNn = nm.QN, &UNn = nm.UN, &RNn = nm.RN, &WNn = nm.WN, &ANn = nm.AN,
8235 const std::size_t F =
8236 r.
QN.rows() >
sn.nstations ? r.
QN.rows() -
sn.nstations :
static_cast<std::size_t
>(0);
8237 const double region_nan = std::numeric_limits<double>::quiet_NaN();
8238 auto region_name = [&](std::size_t f) {
8239 return (f <
sn.regions.size() && !
sn.regions[f].name.empty())
8240 ?
sn.regions[f].name
8241 :
"FCR" + std::to_string(f + 1);
8245 auto region_empty = [&](std::size_t f, std::size_t c) {
8246 return !(d(r.
QN(
sn.nstations + f, c)) > 0.0 || d(r.
RN(
sn.nstations + f, c)) > 0.0 ||
8247 d(r.
TN(
sn.nstations + f, c)) > 0.0);
8249 if (g_json_output) {
8255 p[
"type"] =
"AvgNodeTable";
8257 for (
const char* key : {
"Node",
"JobClass",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
8259 p[key] = line::reg::Json::array();
8260 for (std::size_t i = 0; i < I; ++i)
8261 for (std::size_t c = 0; c < R; ++c) {
8264 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
8265 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
8267 p[
"Node"].push_back(
sn.nodes[i].name);
8268 p[
"JobClass"].push_back(
sn.classes[c].name);
8269 p[
"QLen"].push_back(d(QNn(i, c)));
8270 p[
"Util"].push_back(d(UNn(i, c)));
8271 p[
"RespT"].push_back(d(RNn(i, c)));
8272 p[
"ResidT"].push_back(d(WNn(i, c)));
8273 p[
"ArvR"].push_back(d(ANn(i, c)));
8274 p[
"Tput"].push_back(d(TNn(i, c)));
8276 for (std::size_t f = 0; f < F; ++f)
8277 for (std::size_t c = 0; c < R; ++c) {
8278 if (region_empty(f, c))
continue;
8279 p[
"Node"].push_back(region_name(f));
8280 p[
"JobClass"].push_back(
sn.classes[c].name);
8281 p[
"QLen"].push_back(d(r.
QN(
sn.nstations + f, c)));
8282 p[
"Util"].push_back(region_nan);
8283 p[
"RespT"].push_back(d(r.
RN(
sn.nstations + f, c)));
8284 p[
"ResidT"].push_back(d(r.
WN(
sn.nstations + f, c)));
8285 p[
"ArvR"].push_back(region_nan);
8286 p[
"Tput"].push_back(d(r.
TN(
sn.nstations + f, c)));
8293 std::printf(
"%-16s %-14s %12s %12s %12s %12s %12s %12s\n",
"Node",
"JobClass",
"QLen",
"Util",
8294 "RespT",
"ResidT",
"ArvR",
"Tput");
8295 for (std::size_t i = 0; i < I; ++i)
8296 for (std::size_t c = 0; c < R; ++c) {
8297 if (d(QNn(i, c)) == 0.0 && d(UNn(i, c)) == 0.0 && d(RNn(i, c)) == 0.0 &&
8298 d(WNn(i, c)) == 0.0 && d(ANn(i, c)) == 0.0 && d(TNn(i, c)) == 0.0)
8300 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8301 sn.nodes[i].name.c_str(),
sn.classes[c].name.c_str(), d(QNn(i, c)),
8302 d(UNn(i, c)), d(RNn(i, c)), d(WNn(i, c)), d(ANn(i, c)), d(TNn(i, c)));
8304 for (std::size_t f = 0; f < F; ++f)
8305 for (std::size_t c = 0; c < R; ++c) {
8306 if (region_empty(f, c))
continue;
8307 std::printf(
"%-16s %-14s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8308 region_name(f).c_str(),
sn.classes[c].name.c_str(),
8309 d(r.
QN(
sn.nstations + f, c)), region_nan, d(r.
RN(
sn.nstations + f, c)),
8310 d(r.
WN(
sn.nstations + f, c)), region_nan, d(r.
TN(
sn.nstations + f, c)));
8335int solve_model_cache(
const std::string& file,
const Knobs& k,
const std::string& s) {
8338 std::string banner, suffix;
8340 if (r.
cache.empty())
8342 "-a cache reports the per-Cache hit and miss table and this model has no Cache node, "
8343 "or the solver that ran analyzes none; SolverNC's cache branches are what fill it");
8348 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8349 std::size_t srcnode = 0;
8350 for (std::size_t i = 0; i <
sn.nodes.size(); ++i)
8354 std::string node, cls;
8355 double list, listcap, items, hitp, dhitp, missp, hitr, dhitr, missr, arvr, residt, cost;
8357 std::vector<Row> rows;
8358 const double dnan = std::numeric_limits<double>::quiet_NaN();
8360 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
8362 const typename std::map<std::size_t, line::qn::CacheParam<T> >::const_iterator it =
8363 sn.nodeparam.find(m.
node);
8364 if (it ==
sn.nodeparam.end())
continue;
8365 const std::vector<std::size_t>& hitclass = it->second.hitclass;
8366 const std::size_t h = m.
itemcap.size();
8367 double totcap = 0.0;
8368 for (std::size_t l = 0; l < h; ++l) totcap += m.
itemcap[l];
8369 double totcost = dnan;
8372 for (std::size_t l = 0; l < m.
listcost.size(); ++l)
8376 for (std::size_t cl = 0; cl <
sn.nclasses; ++cl) {
8377 if (cl >= hitclass.size() || hitclass[cl] == 0)
continue;
8380 if (std::isnan(ph) && std::isnan(pm) && std::isnan(pd))
continue;
8381 if (std::isnan(ph)) ph = 0.0;
8382 if (std::isnan(pm)) pm = 0.0;
8383 if (std::isnan(pd)) pd = 0.0;
8386 const double lat = cache_at(m.
latency, cl);
8389 t.node =
sn.nodes[m.
node - 1].name;
8390 t.cls =
sn.classes[cl].name;
8393 t.items =
static_cast<double>(m.
nitems);
8398 t.dhitr = arvr * pd;
8399 t.missr = arvr * pm;
8400 t.arvr = arvr * (pm + pd);
8408 for (std::size_t l = 0; l < m.
hitproblist.cols(); ++l)
8412 for (std::size_t l = 0; l < h; ++l) {
8416 if (std::isnan(phl)) phl = 0.0;
8420 u.list =
static_cast<double>(l + 1);
8426 u.hitr = arvr * phl;
8439 if (g_json_output) {
8441 p[
"type"] =
"AvgCacheTable";
8443 for (
const char* key : {
"Node",
"JobClass",
"List",
"ListCap",
"Items",
"HitProb",
8444 "DelayedHitProb",
"MissProb",
"HitRate",
"DelayedHitRate",
8445 "MissRate",
"ArvR",
"ResidT",
"ListCost"})
8446 p[key] = line::reg::Json::array();
8447 for (std::size_t i = 0; i < rows.size(); ++i) {
8448 p[
"Node"].push_back(rows[i].node);
8449 p[
"JobClass"].push_back(rows[i].cls);
8450 p[
"List"].push_back(rows[i].list);
8451 p[
"ListCap"].push_back(rows[i].listcap);
8452 p[
"Items"].push_back(rows[i].items);
8453 p[
"HitProb"].push_back(rows[i].hitp);
8454 p[
"DelayedHitProb"].push_back(rows[i].dhitp);
8455 p[
"MissProb"].push_back(rows[i].missp);
8456 p[
"HitRate"].push_back(rows[i].hitr);
8457 p[
"DelayedHitRate"].push_back(rows[i].dhitr);
8458 p[
"MissRate"].push_back(rows[i].missr);
8459 p[
"ArvR"].push_back(rows[i].arvr);
8460 p[
"ResidT"].push_back(rows[i].residt);
8461 p[
"ListCost"].push_back(rows[i].cost);
8468 std::printf(
"%-14s %-12s %5s %8s %6s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
"Node",
8469 "JobClass",
"List",
"ListCap",
"Items",
"HitProb",
"DHitProb",
"MissProb",
8470 "HitRate",
"DHitRate",
"MissRate",
"ArvR",
"ResidT",
"ListCost");
8471 for (std::size_t i = 0; i < rows.size(); ++i)
8473 "%-14s %-12s %5g %8g %6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g %10.6g "
8475 rows[i].node.c_str(), rows[i].cls.c_str(), rows[i].list, rows[i].listcap,
8476 rows[i].items, rows[i].hitp, rows[i].dhitp, rows[i].missp, rows[i].hitr,
8477 rows[i].dhitr, rows[i].missr, rows[i].arvr, rows[i].residt, rows[i].cost);
8495int solve_model_item(
const std::string& file,
const Knobs& k,
const std::string& s) {
8498 std::string banner, suffix;
8500 if (r.
cache.empty())
8502 "-a item reports the per-item cache occupancy and this model has no Cache node, or "
8503 "the solver that ran analyzes none");
8505 const double dnan = std::numeric_limits<double>::quiet_NaN();
8508 double item, list, listcap, size, prob, cost, dhq, dhqf;
8510 std::vector<Row> rows;
8511 for (std::size_t c = 0; c < r.
cache.caches.size(); ++c) {
8513 const std::size_t h = m.
itemcap.size();
8519 const std::size_t nit =
8521 for (std::size_t i = 0; i < nit; ++i)
8522 for (std::size_t l = 0; l < h; ++l) {
8524 t.node =
sn.nodes[m.
node - 1].name;
8525 t.item =
static_cast<double>(i + 1);
8526 t.list =
static_cast<double>(l + 1);
8531 t.prob = (l + 1) < m.
itemprob.cols()
8534 t.cost = t.size * t.prob;
8546 "-a item needs a per-item occupancy law and this solve produced none; the NC/MVA "
8547 "cache recursions (isolated and integrated alike) and the delayed-hit retrieval "
8548 "algorithms compute the embedded one, SolverCTMC the time-weighted one, and the "
8551 if (g_json_output) {
8553 p[
"type"] =
"AvgItemTable";
8555 for (
const char* key : {
"Node",
"Item",
"List",
"ListCap",
"Size",
"Prob",
"Cost",
8556 "DelayedHitQLen",
"DelayedHitQLenFull"})
8557 p[key] = line::reg::Json::array();
8558 for (std::size_t i = 0; i < rows.size(); ++i) {
8559 p[
"Node"].push_back(rows[i].node);
8560 p[
"Item"].push_back(rows[i].item);
8561 p[
"List"].push_back(rows[i].list);
8562 p[
"ListCap"].push_back(rows[i].listcap);
8563 p[
"Size"].push_back(rows[i].size);
8564 p[
"Prob"].push_back(rows[i].prob);
8565 p[
"Cost"].push_back(rows[i].cost);
8566 p[
"DelayedHitQLen"].push_back(rows[i].dhq);
8567 p[
"DelayedHitQLenFull"].push_back(rows[i].dhqf);
8574 std::printf(
"%-14s %6s %6s %8s %10s %12s %12s %14s %18s\n",
"Node",
"Item",
"List",
"ListCap",
8575 "Size",
"Prob",
"Cost",
"DelayedHitQLen",
"DelayedHitQLenFull");
8576 for (std::size_t i = 0; i < rows.size(); ++i)
8577 std::printf(
"%-14s %6g %6g %8g %10g %12.8g %12.8g %14.8g %18.8g\n", rows[i].node.c_str(),
8578 rows[i].item, rows[i].list, rows[i].listcap, rows[i].size, rows[i].prob,
8579 rows[i].cost, rows[i].dhq, rows[i].dhqf);
8592int solve_model_sys(
const std::string& file,
const Knobs& k,
const std::string& s) {
8595 std::string banner, suffix;
8602 if (g_json_output) {
8608 p[
"type"] =
"AvgSysTable";
8610 for (
const char* key : {
"Chain",
"JobClasses",
"SysRespT",
"SysTput"})
8611 p[key] = line::reg::Json::array();
8612 for (std::size_t c = 0; c <
sn.nchains; ++c) {
8613 p[
"Chain"].push_back(cn[c]);
8614 p[
"JobClasses"].push_back(cc[c]);
8615 p[
"SysRespT"].push_back(d(sys.
CN[c]));
8616 p[
"SysTput"].push_back(d(sys.
XN[c]));
8621 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner.c_str(),
8623 std::printf(
"%-10s %-24s %14s %14s\n",
"Chain",
"JobClasses",
"SysRespT",
"SysTput");
8624 for (std::size_t c = 0; c <
sn.nchains; ++c)
8625 std::printf(
"%-10s %-24s %14.6g %14.6g\n", cn[c].c_str(), cc[c].c_str(), d(sys.
CN[c]),
8632void print_chain_table(
const char* key,
const char* type,
const char* rowlabel,
8633 const std::vector<std::string>& rows,
8634 const std::vector<std::string>& chains,
8635 const std::vector<std::string>& classes,
8637 const char* banner,
const char* arith,
const char* suffix =
"") {
8639 if (g_json_output) {
8645 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
8646 chains.size(), suffix);
8650 for (
const char* c : {rowlabel,
"Chain",
"JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
8652 p[c] = line::reg::Json::array();
8656 for (std::size_t i = 0; i < rows.size(); ++i)
8657 for (std::size_t c = 0; c < chains.size(); ++c) {
8658 p[rowlabel].push_back(rows[i]);
8659 p[
"Chain"].push_back(chains[c]);
8660 p[
"JobClasses"].push_back(classes[c]);
8661 p[
"QLen"].push_back(d(t.
QN(i, c)));
8662 p[
"Util"].push_back(d(t.
UN(i, c)));
8663 p[
"RespT"].push_back(d(t.
RN(i, c)));
8664 p[
"ResidT"].push_back(d(t.
WN(i, c)));
8665 p[
"ArvR"].push_back(d(t.
AN(i, c)));
8666 p[
"Tput"].push_back(d(t.
TN(i, c)));
8668 emit_analysis<T>(key, p, method);
8671 std::printf(
"%s arith=%s method=%s chains=%zu%s\n", banner, arith, method.c_str(),
8672 chains.size(), suffix);
8673 std::printf(
"%-16s %-10s %-20s %12s %12s %12s %12s %12s %12s\n", rowlabel,
"Chain",
8674 "JobClasses",
"QLen",
"Util",
"RespT",
"ResidT",
"ArvR",
"Tput");
8675 for (std::size_t i = 0; i < rows.size(); ++i)
8676 for (std::size_t c = 0; c < chains.size(); ++c)
8677 std::printf(
"%-16s %-10s %-20s %12.6g %12.6g %12.6g %12.6g %12.6g %12.6g\n",
8678 rows[i].c_str(), chains[c].c_str(), classes[c].c_str(), d(t.
QN(i, c)),
8679 d(t.
UN(i, c)), d(t.
RN(i, c)), d(t.
WN(i, c)), d(t.
AN(i, c)),
8693int solve_model_chain(
const std::string& file,
const Knobs& k,
const std::string& s) {
8696 std::string banner, suffix;
8699 std::vector<std::string> rows;
8700 for (std::size_t i = 0; i <
sn.nstations; ++i) rows.push_back(
sn.stations[i].name);
8701 print_chain_table<T>(
"chain",
"AvgChainTable",
"Station", rows,
8710int solve_model_nodechain(
const std::string& file,
const Knobs& k,
const std::string& s) {
8713 std::string banner, suffix;
8715 const NodeMetrics<T> nm = node_metrics<T>(
sn, r);
8718 std::vector<std::string> rows;
8719 for (std::size_t i = 0; i <
sn.nodes.size(); ++i) rows.push_back(
sn.nodes[i].name);
8720 print_chain_table<T>(
"nodechain",
"AvgNodeChainTable",
"Node", rows,
8728inline bool is_avg_view(
const std::string& analysis) {
8729 return analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
8730 analysis ==
"nodechain";
8743int solve_avg_view(
const std::string& file,
const Knobs& k,
const std::string& s,
8744 const std::string& analysis) {
8745 if (analysis ==
"node")
return solve_model_node<T>(file, k, s);
8746 if (analysis ==
"sys")
return solve_model_sys<T>(file, k, s);
8747 if (analysis ==
"chain")
return solve_model_chain<T>(file, k, s);
8748 return solve_model_nodechain<T>(file, k, s);
8751int solve_model_dispatch(
const std::string& arith,
const std::string& solver,
8752 const std::string& analysis,
const std::string& file,
const Knobs& k) {
8753 std::string s = solver.empty() ?
"auto" : solver;
8754 if (s !=
"mva" && s !=
"auto" && s !=
"fluid" && s !=
"fld" && s !=
"nc" && s !=
"mam" &&
8755 s !=
"ag" && s !=
"ba" && s !=
"ssa" && s !=
"ctmc" && s !=
"uq" && s !=
"env" &&
8756 s !=
"lqns" && s !=
"ldes" && s !=
"jmt")
8758 "the model-solving path ports -s mva, nc, ctmc, mam, ag, ba, ssa, fluid, ldes, jmt, "
8759 "uq, env and lqns (got '" + s +
"'); other solvers remain API-only");
8766 if ((k.verbose && s !=
"ldes") || k.remote || !k.remote_url.empty() ||
8767 (k.keep && s !=
"lqns" && s !=
"jmt"))
8769 "--keep, --verbose, --remote and --remote-url describe the child process of an "
8770 "external solver; on this path -s jmt and -s lqns run one and take --keep, -s ldes "
8771 "runs one and takes --verbose (which echoes the resolved engine command line), and "
8772 "no path takes --remote or --remote-url");
8773 if (k.timeout_seconds && s !=
"lqns")
8775 "--timeout is the deadline of an external solver's child process; on this path only "
8776 "-s lqns runs one");
8782 if (!k.fork_join.empty() && s !=
"mva" && s !=
"nc")
8784 "--fork-join selects the fork-join transform of the shared mean-value fixed point "
8785 "and is read by -s mva and -s nc; -s " + s +
8786 " either simulates or enumerates the fork and applies no transform");
8791 if (k.warmupfrac >= 0.0 && s !=
"ssa")
8793 "--warmupfrac discards a leading fraction of a SIMULATED path before the means are "
8794 "taken and is read by -s ssa; -s " + s +
8795 " has no path to discard (the LDES engine takes --ldes-warmupfrac)");
8796 if (k.pstar > 0.0 && s !=
"fluid" && s !=
"fld")
8798 "--pstar is the exponent of the fluid p-norm smoothing of the drift and is read by "
8799 "-s fluid; -s " + s +
" integrates no drift");
8800 if ((!k.busy_orders.empty() || !k.busy_subnet.empty()) && analysis !=
"busyperiod")
8802 "--busyperiod and --busyperiod-subnet name the orders and the subnetwork of "
8803 "-a busyperiod; got -a " + analysis);
8817 if (analysis !=
"avg" && analysis !=
"posterior" && analysis !=
"interval")
8819 "SolverUQ ports -a avg (the prior-weighted expectation), -a posterior (the "
8820 "per-design-point table) and -a interval (the support-only range); got '" +
8822 if (k.uq_solver.empty())
8824 "-s uq needs --uq-solver: UQ computes nothing itself, it expands the Prior and "
8825 "runs another solver at each design point (the C++ spelling of UQ(model, "
8826 "@SolverMVA)). Naming one here by default would attribute the numbers to an "
8827 "engine the caller never chose");
8828 if (k.t1 >= 0.0 || k.node || !k.notation.empty())
8830 "--tspan, --node and --notation name a transient horizon, a stateful node and an "
8831 "ODE document; SolverUQ reports steady-state means over a design of models and "
8832 "has none of the three");
8833 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty())
8835 "--no-interlocking, --interlock-*, --repeat and --layer-solver are options of the layered solver "
8836 "and apply to -i lqnx; a Network model has no layers to interlock");
8837 if (arith ==
"double")
return solve_model_uq<double>(file, k, analysis);
8838 if (arith ==
"exact")
return solve_model_uq<line::Rational>(file, k, analysis);
8839 if (arith ==
"real:16")
return solve_model_uq<line::Real<16> >(file, k, analysis);
8840 if (arith ==
"real" || arith ==
"real:32")
8841 return solve_model_uq<line::Real<32> >(file, k, analysis);
8842 if (arith ==
"real:64")
return solve_model_uq<line::Real<64> >(file, k, analysis);
8843 if (arith ==
"real:128")
return solve_model_uq<line::Real<128> >(file, k, analysis);
8844 if (arith ==
"real:256")
return solve_model_uq<line::Real<256> >(file, k, analysis);
8845 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8854 if (analysis !=
"avg")
8856 "SolverENV reports -a avg, the environment-blended means; getEnsembleAvg is its "
8857 "only metric entry in the reference too (got '" + analysis +
"')");
8858 if (k.samples || k.seed)
8860 "--samples and --seed describe a simulation; SolverENV iterates a fixed point over "
8861 "transient stage solves and draws nothing");
8866 if (k.has_cutoff() && k.stage_solver !=
"ctmc")
8868 "--cutoff bounds the open population of an enumerated state space and applies to "
8869 "-s env only beside --stage-solver ctmc; the fluid stages of this ensemble "
8870 "enumerate no states");
8871 if (k.node || !k.notation.empty())
8873 "--node and --notation name a stateful node and an ODE document of ONE network; "
8874 "an Environment holds a network per stage and "
8875 "SolverENV reports the blend over them");
8876 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty())
8878 "--no-interlocking, --interlock-*, --repeat and --layer-solver are options of the layered solver "
8879 "and apply to -i lqnx; an Environment has stages, not layers");
8882 "--tspan on the ENV path states the transient HORIZON each stage solve integrates "
8883 "to, and every stage starts from its entry state at 0; a nonzero t0 would name a "
8884 "start the coupling has no state for");
8892 const std::string coupling =
8893 (k.method.empty() || k.method ==
"default" || k.method ==
"mean"
8894 || k.method ==
"blend" || k.method ==
"blending")
8897 if (k.tran_points && coupling ==
"statevec")
8899 "--tran-points is the mean-field coupling's quadrature grid; the state-vector "
8900 "coupling carries the whole joint law across a switch and sums over no such grid, "
8901 "so the value would be accepted and never used");
8908 if (coupling ==
"statedep")
8910 "--method statedep makes each environment transition depend on the state its "
8911 "stage is left in, through a rate function per arc that no model.json can carry "
8912 "(the reference cannot serialize resetEnvRatesFun either); it is reachable from "
8913 "the C++ API, through Environment::set_env_rate_reset");
8914 if ((k.tran_points || k.t1 >= 0.0) && (coupling ==
"avg" || coupling ==
"dec"))
8916 "--tran-points and --tspan state the grid and the horizon of a TRANSIENT stage "
8917 "solve; the closed-form limits --method avg and --method dec solve in steady "
8918 "state and carry nothing across a switch, so both would be accepted and never "
8920 if (arith !=
"double" && coupling !=
"statevec")
8922 "SolverENV solves a stage with the fluid analyzer on every method but the "
8923 "state-vector one -- the mean-field coupling transiently, the avg and dec limits "
8924 "in steady state -- and that analyzer is LSODA's and therefore double; --arith " +
8926 " reaches ENV only through the state-vector coupling (--method statevec)");
8927 if (arith ==
"double")
return solve_model_env<double>(file, k);
8928 if (arith ==
"exact")
return solve_model_env<line::Rational>(file, k);
8929 if (arith ==
"real:16")
return solve_model_env<line::Real<16> >(file, k);
8930 if (arith ==
"real" || arith ==
"real:32")
8931 return solve_model_env<line::Real<32> >(file, k);
8932 if (arith ==
"real:64")
return solve_model_env<line::Real<64> >(file, k);
8933 if (arith ==
"real:128")
return solve_model_env<line::Real<128> >(file, k);
8934 if (arith ==
"real:256")
return solve_model_env<line::Real<256> >(file, k);
8935 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
8937 if (!k.uq_solver.empty())
8939 "--uq-solver names the engine SolverUQ runs at each design point and applies to -s uq; "
8940 "'" + s +
"' solves one model, not a design of them");
8943 "--tran-points is the resolution of the transient grid SolverENV sums its stage exit "
8944 "metrics over and applies to -s env; '" + s +
"' has no such quadrature");
8950 const bool ldes_knob = !k.ldes_tranfilter.empty() || k.ldes_warmupfrac >= 0.0 ||
8951 !k.ldes_cimethod.empty() || k.ldes_cnvgon || k.ldes_slotted ||
8952 k.ldes_replications > 0 || k.ldes_numthreads > 0 ||
8953 k.ldes_maxtime > 0.0 || !k.ldes_initsol.empty() ||
8954 !k.ldes_rest_url.empty();
8955 if (ldes_knob && s !=
"ldes" && s !=
"auto")
8957 "the --ldes-* flags are the discrete-event engine's own settings (warmup filter, "
8958 "confidence-interval estimator, slot lattice, replications, warm-start placement) and "
8959 "apply to -s ldes; '" + s +
"' has none of them");
8960 if (k.jmt_replications > 0 && s !=
"jmt")
8962 "--jmt-replications is SolverJMT's transient ensemble size (-s jmt -a tran / -a "
8963 "tranprob); '" + s +
"' has none (-s ldes takes --ldes-replications)");
8965 if (arith !=
"double")
8967 "SolverJMT is a client of the Java Modelling Tools engine, which simulates in "
8968 "double and reports in double; --arith " + arith +
8969 " would label a double answer with an arithmetic that never touched it");
8977 if (analysis !=
"avg" && analysis !=
"cdf" && analysis !=
"trancdf" &&
8978 analysis !=
"trancdfpasst" && analysis !=
"prob" && analysis !=
"tran" &&
8979 analysis !=
"tranprob" && analysis !=
"sample" && !is_avg_view(analysis))
8981 "-s jmt reports -a avg (the JSIM or JMVA mean table), its four views -a node, "
8982 "-a sys, -a chain and -a nodechain, -a cdf (the empirical response-time law read "
8983 "back from the JMT logs, preloaded at the rounded steady-state queue lengths), "
8984 "-a tran-cdf-respt and -a tran-cdf-passt (the same logged run from the default "
8985 "initial state, so the samples cover the transient), -a prob (the time each "
8986 "declared state is held for along the logged trajectory), -a tran (the transient "
8987 "means over --jmt-replications independent replications), -a tranprob (the same "
8988 "replications' aggregate state law at one station) and -a sample (one logged "
8989 "trajectory); the DETAILED laws -a states and getTranProb have no counterpart, "
8990 "since a JMT log records per-class job counts and nothing about the buffer order "
8991 "or the service phase");
8997 if (!k.method.empty() &&
8998 std::find(valid.begin(), valid.end(), k.method) == valid.end())
9000 "SolverJMT methods are default, jsim and the jmva family (jmva, jmva.amva, "
9001 "jmva.mva, jmva.recal, jmva.comom, jmva.chow, jmva.bs, jmva.aql, jmva.lin, "
9002 "jmva.dmlin); got '" + k.method +
"'");
9005 const bool jmt_replicated = analysis ==
"tran" || analysis ==
"tranprob";
9006 if (k.iter_max >= 0)
9008 "--iter_max is not a SolverJMT option: the number of independent replications -a "
9009 "tran and -a tranprob average over is --jmt-replications (options.config."
9010 "replications, default 10)");
9011 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || !k.multiserver.empty())
9013 "--tol, --iter_tol and --multiserver configure a fixed-point iteration; JSIM "
9014 "simulates a sample path and JMVA takes its tolerance from the exported document. "
9015 "The simulation's stopping rule is --samples");
9016 if (k.jmt_replications > 0 && !jmt_replicated)
9018 "--jmt-replications sizes the transient ensemble of -a tran and -a tranprob; "
9019 "steady-state JSIM (-a " + analysis +
") is one run and reads no replication count");
9022 "--cutoff truncates an enumerated state space; JMT enumerates none");
9029 const bool jmt_node_arm =
9030 analysis ==
"prob" || analysis ==
"tranprob" || analysis ==
"sample";
9031 if ((k.node && !jmt_node_arm) || k.jobclass || !k.marg_states.empty())
9033 "--node, --class and --marg-states select the marginal law of one (node, class); "
9034 "the JMT arms report tables over every station and class, and --node applies to "
9035 "-a prob (which station --state overrides), -a tranprob (whose state law) and "
9036 "-a sample (whose trajectory)");
9037 if (!k.state.empty() && analysis !=
"prob")
9039 "--state names the state a probability is asked about and applies to -a prob");
9040 if (!k.state.empty() && !k.node)
9042 "--state is the per-class job count of ONE station and needs --node to say "
9043 "which; a bare count vector cannot be matched against a whole network");
9044 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
9046 "--notation, --symbolic and --equilibria describe an exported ODE document; JMT "
9047 "integrates no ODE");
9048 if (!k.cdf_algorithm.empty())
9050 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
9051 "JMT response-time law is the ecdf of the passages its loggers recorded and is "
9052 "not computed from a transform");
9053 if (is_avg_view(analysis))
return solve_avg_view<double>(file, k,
"jmt", analysis);
9054 return solve_model_jmt(file, k, analysis);
9057 if (arith !=
"double")
9059 "SolverLDES is a client of the SSJ engine, which simulates in double and reports "
9060 "in double; --arith " + arith +
9061 " would label a double answer with an arithmetic that never touched it");
9069 if (!k.method.empty() && k.method !=
"default" && k.method !=
"para" && k.method !=
"parallel")
9071 "SolverLDES has the methods 'default' and 'parallel' (alias 'para'; listValidMethods returns "
9072 "exactly those in every codebase); got '" + k.method +
"'");
9074 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0 || !k.multiserver.empty())
9076 "--tol, --iter_tol, --iter_max and --multiserver are the knobs of a fixed-point "
9077 "iteration; LDES simulates a sample path and iterates nothing. Its stopping rule "
9078 "is --samples, or --ldes-cnvgon with --ldes-cnvgtol");
9081 "--cutoff truncates an enumerated state space; a simulator visits the states the "
9082 "sample path reaches and enumerates none");
9083 if (k.jobclass || !k.marg_states.empty() || (k.node && analysis !=
"prob"))
9085 "--class and --marg-states select a marginal law of one (node, class), and --node "
9086 "applies to -a prob beside --state; the other LDES arms report tables over every "
9087 "station and class");
9088 if (!k.state.empty() && (analysis !=
"prob" || !k.node))
9090 "--state is getProbAggr(node, state)'s per-class job count of ONE station: it "
9091 "applies to -s ldes -a prob and needs --node to say which");
9092 if (!k.notation.empty() || !k.symbolic.empty() || k.equilibria)
9094 "--notation, --symbolic and --equilibria describe an exported ODE document; LDES "
9095 "integrates no ODE");
9096 if (!k.cdf_algorithm.empty())
9098 "--cdf-algorithm selects between the two sojourn-time INVERSIONS of -s nc; the "
9099 "LDES response-time law is the ecdf of the samples the engine recorded and is not "
9100 "computed from a transform");
9101 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty())
9103 "--no-interlocking, --interlock-*, --repeat and --layer-solver are options of the layered solver "
9104 "and apply to -i lqnx; the LDES layered path runs in the JAR's own ensemble "
9105 "backend and has no JSON interface to reach from here");
9106 if (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step >= 0.0)
9108 "--sens-method, --sens-scheme and --sens-step configure the layered sensitivity "
9109 "table; LDES reports no sensitivity");
9110 if (analysis ==
"tran" && !(k.t1 >= 0.0))
9112 "-s ldes -a tran needs --tspan <t1> or --tspan <t0>:<t1>: a trajectory over an unstated "
9113 "horizon is not a quantity, and the engine only records buckets once a timespan "
9114 "makes the run transient");
9115 if (k.t1 >= 0.0 && analysis !=
"tran")
9117 "--tspan names the horizon of -a tran; -a sample runs over [0, --samples], which "
9118 "is the horizon runTransientJson uses, and the other arms are steady state");
9125 if ((analysis ==
"trancdf" || analysis ==
"trancdfpasst") &&
9126 k.verbosity !=
"silent")
9127 std::fprintf(stderr,
9128 "Warning: -a %s is the ecdf of the per-job response times the run "
9129 "observed, the same curve -a cdf reports; the reference's LDES "
9130 "getTranCdfRespT reads the same samples\n",
9135 if ((kldes.method ==
"parallel" || kldes.method ==
"para") && kldes.ldes_replications <= 1)
9136 kldes.ldes_replications = 8;
9137 if (is_avg_view(analysis))
return solve_avg_view<double>(file, kldes,
"ldes", analysis);
9138 return solve_model_ldes(file, kldes, analysis);
9148 if (analysis ==
"aoi")
9150 "-a aoi is the AoI branch of the fluid 'mfq' method and no other engine reports "
9151 "it, so SolverAUTO does not choose for it: ask for it by name with -s fluid");
9152 const AutoPlan plan = choose_auto_plan_dispatch(arith, file, analysis, k.method);
9159 if (plan.order.size() == 1) {
9164 kk.method = plan.method;
9165 std::printf(
"SolverAUTO selected %s%s\n", plan.order[0].c_str(), plan.note.c_str());
9166 return solve_model_dispatch(arith, plan.order[0], analysis, file, kk);
9168 std::string first_error;
9169 for (std::size_t i = 0; i < plan.order.size(); ++i) {
9171 kk.method = (i == 0) ? plan.method : std::string();
9173 std::printf(
"SolverAUTO selected %s%s\n", plan.order[i].c_str(),
9176 std::printf(
"SolverAUTO retrying with %s\n", plan.order[i].c_str());
9178 return solve_model_dispatch(arith, plan.order[i], analysis, file, kk);
9180 if (first_error.empty()) first_error = plan.order[i] +
": " + e.what();
9181 std::printf(
"SolverAUTO: %s cannot serve this run (%s)\n", plan.order[i].c_str(),
9186 "SolverAUTO: every candidate refused this run. The chosen engine reported -- " +
9193 const bool is_sim = (s ==
"ssa");
9201 const bool is_cftp =
9202 (s ==
"nc" && (k.method ==
"cftp" || k.method ==
"cftp.approx"));
9217 const bool draws_samples =
9218 is_sim || (s ==
"ctmc" && analysis ==
"sample") || is_cftp || nc_draws;
9219 if (k.samples && !draws_samples)
9221 "-s ctmc -a sample and to the stochastic -s nc methods "
9222 "(mci, imci, ls, is, sampling, mcmc, cftp, cftp.approx); '" +
9223 s +
"' has no sample count");
9224 if (k.seed && !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 +
"' draws no random numbers");
9232 if ((k.mdd_tol > 0.0 || k.mdd_maxiter > 0) && !(s ==
"ctmc" && k.method ==
"mdd"))
9234 "--mdd-tol and --mdd-maxiter set the coupled level iteration of -s ctmc --method mdd; "
9235 "'" + s +
" / " + (k.method.empty() ? std::string(
"default") : k.method) +
9236 "' iterates no levels");
9245 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")) &&
9246 !(s ==
"fluid" || s ==
"fld") && !(s ==
"mam" && analysis ==
"tran"))
9248 "--tspan sets the horizon of a transient analysis and applies to -s ctmc -a tran, "
9249 "-a tranprob and -a tranreward, to -s mam -a tran and to -s fluid; '" + s +
" / " +
9250 analysis +
"' integrates no forward equation");
9258 if (k.node && !(s ==
"ctmc" && (analysis ==
"tranprob" || analysis ==
"sample")) &&
9259 !(s ==
"ctmc" && analysis ==
"prob" && !k.state.empty()) &&
9260 !(s ==
"nc" && analysis ==
"prob" && !k.state.empty()) &&
9261 !(s ==
"ssa" && analysis ==
"sample") && !(s ==
"mam" && analysis ==
"prob") &&
9262 !((s ==
"mva" || s ==
"nc") && analysis ==
"marg"))
9264 "--node selects the stateful node a state query is labelled by and applies to -s ctmc "
9265 "-a tranprob and -a sample, to -s ctmc|nc -a prob beside --state, to -s ssa -a sample, "
9266 "to -s mam -a prob and to -s mva|nc -a marg; '" + s +
" / " + analysis +
9267 "' reports the whole network");
9271 if ((k.jobclass || !k.marg_states.empty()) && !(s ==
"mva" && analysis ==
"marg"))
9273 "--class and --marg-states are the job class and the state list of getProbMarg and "
9274 "apply to -s mva -a marg; '" + s +
" / " + analysis +
9275 "' reports every class over its own range");
9279 if (!k.notation.empty() && !((s ==
"fluid" || s ==
"fld") && analysis ==
"odes"))
9281 "--notation selects the form of the exported ODE document and applies to -s fluid -a "
9282 "odes; '" + s +
" / " + analysis +
"' exports no equations");
9285 if ((!k.symbolic.empty() || k.equilibria) &&
9286 !((s ==
"fluid" || s ==
"fld") && analysis ==
"jacobian"))
9288 "--symbolic selects the computer-algebra backend and --equilibria asks it for the "
9289 "solutions of f(x) = 0; both apply to -s fluid -a jacobian, and '" + s +
" / " +
9290 analysis +
"' consults no backend");
9299 if (!k.state.empty() &&
9300 !(analysis ==
"prob" && (s ==
"ctmc" || s ==
"auto" || s ==
"jmt" || s ==
"nc")))
9302 "--state names the state `getProb(node, state)` asks about and applies to -a prob "
9303 "under -s ctmc, -s nc and -s jmt, the arms whose answer is indexed by a state; '" + s +
9304 " / " + analysis +
"' reports a mean or a law over all of them");
9305 if (k.events && analysis !=
"sample")
9307 "--events is the length of ONE sampled trajectory and applies to -a sample; use "
9308 "--samples for a solver's run length ('" + s +
" / " + analysis +
"')");
9309 if (!k.percentiles.empty() && !(s ==
"mam" && (analysis ==
"cdf" || analysis ==
"cdfpasst" ||
9310 analysis ==
"perct")))
9312 "--percentiles names the levels getPerctRespT is read at and applies to -s mam -a "
9313 "perct-respt (and to the percentiles printed beside -a cdf); '" + s +
" / " +
9314 analysis +
"' inverts no response-time law");
9315 if (!k.reward_name.empty() && analysis !=
"rewardvalue")
9317 "--reward-name selects which declared reward -a reward-value returns the value "
9318 "function of; -a reward returns every reward's steady-state expectation and needs no "
9319 "name ('" + s +
" / " + analysis +
"')");
9320 if (k.timestep > 0.0 &&
9322 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
9324 "--timestep is the fixed output grid of a transient CTMC solve, `options.timestep` of "
9325 "ctmc_transient.m, and applies to -s ctmc -a tran, -a tranprob and -a tranreward; the "
9327 "and simulated transients report the points their own integrator or engine produced "
9328 "('" + s +
" / " + analysis +
"')");
9329 if ((!k.rate_sched.empty() || k.ctmc_tv_ngrid > 0) && !(s ==
"ctmc" && analysis ==
"tran"))
9331 "--rate-sched (and --ctmc-tv-ngrid) makes the CTMC generator time-inhomogeneous, "
9332 "`options.config.rate_sched` of solver_ctmc_transient_analyzer.m, and applies to "
9333 "-s ctmc -a tran only ('" + s +
" / " + analysis +
"')");
9334 if ((!k.transient_method.empty() || k.fau_epsilon > 0.0 || k.fau_delta >= 0.0) &&
9336 (analysis ==
"tran" || analysis ==
"tranprob" || analysis ==
"tranreward")))
9338 "--transient-method (and --fau-epsilon / --fau-delta) selects how the CTMC forward "
9339 "equation is advanced, `options.config.transient_method` of "
9340 "solver_ctmc_transient_analyzer.m, and applies to -s ctmc -a tran, -a tranprob and "
9341 "-a tranreward; every other analysis solves no forward equation ('" +
9342 s +
" / " + analysis +
"')");
9346 if (!k.cdf_algorithm.empty() && !(s ==
"nc" && analysis ==
"cdf"))
9348 "--cdf-algorithm selects the sojourn-time inversion of the NC response-time "
9349 "distribution and applies to -s nc -a cdf; '" + s +
" / " + analysis +
9350 "' inverts no generating function");
9355 const bool passage_arm =
9356 (s ==
"ctmc" && (analysis ==
"firstpasst" || analysis ==
"firstpasstmom"));
9357 if ((!k.passage_from.empty() || !k.passage_into.empty() || k.passage_orders > 0) &&
9360 "--passage-from, --passage-into and --passage-orders name the state sets and the "
9361 "moment order of -s ctmc -a firstpasst / firstpasstmom; '" + s +
" / " + analysis +
9362 "' times no state-set passage");
9363 if (!k.passage_method.empty() && !(s ==
"ctmc" && analysis ==
"firstpasst"))
9365 "--passage-method selects the transform inversion of -s ctmc -a firstpasst; '" + s +
9366 " / " + analysis +
"' inverts none (the moments arm solves for them directly)");
9369 if (k.method_perm !=
"exact" && !(s ==
"nc" && analysis ==
"sysmarg"))
9371 "--perm-engine selects the permanent estimator of the NC joint total-queue-length "
9372 "law and applies to -s nc -a sysmarg; '" + s +
" / " + analysis +
9373 "' evaluates no permanent");
9379 const bool nc_sens = s ==
"nc" && analysis ==
"sens";
9380 if (!nc_sens && (!k.sens_method.empty() || !k.sens_scheme.empty() || k.sens_step > 0.0))
9382 "--sens-method, --sens-scheme and --sens-step select the branch of a sensitivity "
9383 "table and apply to -i lqnx -a sens or to -s nc -a sens; '" + s +
" / " + analysis +
9384 "' differentiates nothing");
9385 if (k.no_interlocking || k.interlock_knobs_given() || k.repeat > 0 || !k.layer_solver.empty() ||
9386 !k.ln_transient.empty() || !k.ln_transient_channels.empty())
9388 "--no-interlocking, --interlock-*, --repeat, --layer-solver and --ln-transient* are options "
9389 "of the layered solver and apply to -i lqnx; a Network model has no layers to "
9391 if (s ==
"ba" && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
9393 "--tol, --iter_tol and --iter_max do not apply to -s ba: a bound is a closed form, "
9394 "with nothing to converge");
9398 if (s !=
"ba" && (!k.qrf_params.empty() || !k.qrf_alpha.empty()))
9400 "--qrf-params and --qrf-alpha parameterise the QRF reduction bounds and apply to "
9401 "-s ba; '" + s +
"' solves no reduction program");
9402 if (s !=
"ba" && k.level > 0)
9404 "--level is the hierarchy level of the SolverBA bound families and applies to -s ba; "
9405 "'" + s +
"' has no bound hierarchy");
9406 if (is_sim && (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0))
9408 "--tol, --iter_tol and --iter_max do not apply to -s ssa: a sample path is not an "
9409 "iteration; use --samples to set its length");
9410 if (s ==
"mam" && k.iter_tol >= 0.0)
9412 "--iter_tol is not a SolverMAM option (MamOptions carries tol and iter_max); "
9414 if (s ==
"ag" && k.iter_tol >= 0.0)
9416 "--iter_tol is not a SolverAG option (AgOptions carries tol and iter_max, the "
9417 "tolerance and the sweep budget of the reversed-rate fixed point); use --tol");
9424 if (k.max_states >= 0 && s !=
"ag")
9426 "--max-states truncates the queue-length dimension of a SolverAG agent and applies "
9427 "to -s ag; '" + s +
"' truncates no agent (use --cutoff for a CTMC state space)");
9433 if (k.has_cutoff() && s !=
"ctmc" && !(s ==
"mam" && analysis ==
"prob") && s !=
"env")
9435 "--cutoff bounds the open population of a CTMC state space, and the level truncation "
9436 "of -s mam -a prob; '" + s +
" / " + analysis +
"' enumerates no states");
9442 if (!k.cutoff_mat.empty() && s !=
"ctmc")
9444 "--cutoff as a per-(station,class) matrix bounds an enumerated state space per "
9445 "station and applies to -s ctmc; '" + s +
"' takes one number");
9450 if (!k.stage_solver.empty() && s !=
"env")
9452 "--stage-solver names the solver each stage of a random environment is run with and "
9453 "applies to -s env; '" + s +
"' has no stages");
9454 if (!k.stage_solver.empty() && k.stage_solver !=
"fluid" && k.stage_solver !=
"ctmc" &&
9455 k.stage_solver !=
"mam")
9457 "--stage-solver '" + k.stage_solver +
9458 "' is not available: the environment coupling needs a TRANSIENT stage solve, and only "
9459 "the fluid analyzer, the enumerated CTMC and the flattened LD-QBD provide one in this "
9464 if (!k.map_env.empty() && k.map_env !=
"default" && k.map_env !=
"off")
9466 "' is not a value: use 'default' or 'off'");
9467 if (!k.map_env_method.empty() && k.map_env_method !=
"auto" && k.map_env_method !=
"dec" &&
9468 k.map_env_method !=
"avg" && k.map_env_method !=
"meanfield")
9470 "--map-env-method '" + k.map_env_method +
9471 "' is not an environment recombination: use 'auto', 'meanfield', 'dec' or 'avg'");
9472 if ((!k.map_env.empty() || !k.map_env_method.empty() || k.map_env_maxstages) &&
9473 (s ==
"ba" || s ==
"env"))
9475 "--map-env* asks a solver to fall back on a random-environment image of a non-renewal "
9476 "process; '-s " + s +
9477 "' takes no such fallback (a bound must not be computed on an approximation, and the "
9478 "environment solver already takes a model with stages)");
9483 if (k.stage_solver ==
"mam" && k.method !=
"statevec")
9485 "--stage-solver mam applies to -s env --method statevec: the LD-QBD backend flattens "
9486 "its blocks into a generator the state-vector coupling propagates a distribution "
9487 "across, and the mean-field coupling carries marginal MEANS instead");
9492 if ((k.fj_accuracy > 0 || !k.fj_tmode.empty()) && s !=
"mam")
9494 "--fj-accuracy and --fj-tmode configure the FJ_codes fork-join approximation of "
9495 "solver_mam_fj.m and apply to -s mam; '" + s +
"' does not run it");
9500 if (!k.timescale.empty() && s !=
"mam")
9502 "--timescale selects the time scale of the MAM discrete-time path and applies to "
9503 "-s mam; '" + s +
"' does not read it (SolverNC takes --slotted)");
9522 if (analysis ==
"node" || analysis ==
"sys" || analysis ==
"chain" ||
9523 analysis ==
"nodechain" || analysis ==
"cache" || analysis ==
"item") {
9524 const bool is_sim_engine = (s ==
"ssa" || s ==
"fluid");
9544 const bool cache_table = (analysis ==
"cache" || analysis ==
"item");
9545 const bool sim_cache_ok = (analysis ==
"cache");
9548 const bool ag_view = (s ==
"ag" && !cache_table);
9549 if ((s !=
"mva" && s !=
"auto" && s !=
"nc" && s !=
"mam" && s !=
"ba" && s !=
"ctmc" &&
9550 !ag_view && !is_sim_engine) ||
9551 (cache_table && is_sim_engine && !sim_cache_ok))
9557 ? std::string(
"-a node reports the per-node table of -s mva, nc, mam, ag, ba, "
9558 "ctmc, ssa, fluid, jmt, ldes and auto; '")
9559 :
"-a " + analysis +
9560 " is a view of the station AvgResult and is reported by -s mva, nc, "
9561 "mam, " + (analysis ==
"item" ?
"" :
"ag, ") +
"ba, ctmc" +
9562 (analysis ==
"item" ?
"" :
", ssa, fluid, jmt, ldes") +
" and auto; '") +
9563 s +
"' does not return the station AvgResult it is built from");
9569 if (is_sim_engine && arith !=
"double")
9571 "-a " + analysis +
" under -s " + s +
9572 " is read off a " + (s ==
"ssa" ?
"sample path" :
"fluid trajectory") +
9573 ", which is transcendental; rerun with --arith double (got '" + arith +
"')");
9574 const std::string eng = (s ==
"auto") ? std::string(
"mva") : s;
9575#define LINE_CLI_TABLE_LADDER(FN) \
9577 if (arith == "double") return FN<double>(file, k, eng); \
9578 if (arith == "exact") return FN<line::Rational>(file, k, eng); \
9579 if (arith == "real:16") return FN<line::Real<16> >(file, k, eng); \
9580 if (arith == "real" || arith == "real:32") return FN<line::Real<32> >(file, k, eng); \
9581 if (arith == "real:64") return FN<line::Real<64> >(file, k, eng); \
9582 if (arith == "real:128") return FN<line::Real<128> >(file, k, eng); \
9583 if (arith == "real:256") return FN<line::Real<256> >(file, k, eng); \
9591#undef LINE_CLI_TABLE_LADDER
9592 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9601 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"gen" && analysis !=
"states" &&
9602 analysis !=
"tran" && analysis !=
"tranprob" && analysis !=
"tranreward" &&
9603 analysis !=
"sample" && analysis !=
"reward" && analysis !=
"rewardvalue" &&
9604 analysis !=
"cdf" && analysis !=
"sens" && analysis !=
"firstpasst" &&
9605 analysis !=
"firstpasstmom")
9607 "the CTMC solver ports -a avg, node, sys, chain, nodechain, prob, gen, states, "
9608 "tran, tranprob, tranreward, sample, reward, reward-value, cdf, first-passt, "
9609 "first-passt-moments and sens (got '" + analysis +
"')");
9615 if (k.method ==
"mdd" && analysis !=
"avg")
9617 "the '" + k.method +
9618 "' method never builds the explicit generator, so it serves -a avg only (got '" +
9619 analysis +
"'); use --method default for the state-space analyses");
9620 if (k.tol >= 0.0 || k.iter_tol >= 0.0 || k.iter_max >= 0)
9622 "--tol, --iter_tol and --iter_max do not apply to -s ctmc: the stationary vector "
9623 "is obtained by a direct solve of pi Q = 0, with nothing to converge. The mdd "
9624 "method's level iteration has --mdd-tol and --mdd-maxiter of its own");
9628 if (arith ==
"double")
return solve_model_ctmc<double>(file, k, analysis);
9629 if (arith ==
"exact")
return solve_model_ctmc<line::Rational>(file, k, analysis);
9630 if (arith ==
"real:16")
return solve_model_ctmc<line::Real<16> >(file, k, analysis);
9631 if (arith ==
"real" || arith ==
"real:32")
9632 return solve_model_ctmc<line::Real<32> >(file, k, analysis);
9633 if (arith ==
"real:64")
return solve_model_ctmc<line::Real<64> >(file, k, analysis);
9634 if (arith ==
"real:128")
return solve_model_ctmc<line::Real<128> >(file, k, analysis);
9635 if (arith ==
"real:256")
return solve_model_ctmc<line::Real<256> >(file, k, analysis);
9636 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9648 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"cdf" &&
9649 analysis !=
"cdfpasst" && analysis !=
"perct" && analysis !=
"tran" &&
9650 analysis !=
"internals")
9652 "the MAM solver ports -a avg, node, prob, cdf, cdf-passt, perct-respt, tran and "
9653 "internals (got '" + analysis +
"')");
9654 if (arith !=
"double")
9656 "the MAM solver fits phase-type representations, whose fitter requires "
9657 "transcendental arithmetic; rerun with --arith double (got '" + arith +
"')");
9658 if (analysis ==
"tran" && k.t1 < 0.0)
9660 "-s mam -a tran integrates the transient queue length over a horizon and there is "
9661 "no default for it; pass --tspan t0 t1");
9662 if (analysis ==
"prob")
return solve_model_mam_prob<double>(file, k);
9663 if (analysis ==
"cdf")
return solve_model_mam_cdf<double>(file, k,
"cdf",
"CdfRespT");
9670 if (analysis ==
"cdfpasst")
9671 return solve_model_mam_cdf<double>(file, k,
"cdfpasst",
"CdfPassT");
9672 if (analysis ==
"perct")
return solve_model_mam_perct<double>(file, k);
9673 if (analysis ==
"tran")
return solve_model_mam_tran<double>(file, k);
9674 if (analysis ==
"internals")
return solve_model_mam_internals<double>(file, k);
9675 return solve_model_mam<double>(file, k);
9683 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"sample")
9686 if (arith !=
"double")
9688 "an SSA sample path is generated from exponential clocks, which are "
9689 "transcendental; rerun with --arith double (got '" + arith +
"')");
9690 if (analysis ==
"prob")
return solve_model_ssa_prob<double>(file, k);
9691 if (analysis ==
"sample")
return solve_model_ssa_sample<double>(file, k);
9692 return solve_model_ssa<double>(file, k);
9695 if (analysis !=
"avg" && analysis !=
"prob" && analysis !=
"marg" &&
9696 analysis !=
"sysmarg" && analysis !=
"cdf" && analysis !=
"sens" &&
9697 analysis !=
"normconst" && analysis !=
"busyperiod")
9699 "the NC solver ports -a avg, -a node, -a prob, -a marg, -a sysmarg, -a cdf, "
9700 "-a sens, -a normconst and -a busyperiod (got '" + analysis +
"')");
9705 if ((k.method ==
"cftp" || k.method ==
"cftp.approx") && analysis !=
"avg")
9707 "the '" + k.method +
9708 "' method draws stationary states and yields no normalizing constant, so it "
9709 "serves -a avg only (got '" + analysis +
"'); use another --method for the "
9710 "other NC analyses");
9711 if (analysis ==
"busyperiod") {
9712 if (arith ==
"double")
return solve_model_nc_busyp<double>(file, k);
9713 if (arith ==
"exact")
return solve_model_nc_busyp<line::Rational>(file, k);
9714 if (arith ==
"real:16")
return solve_model_nc_busyp<line::Real<16> >(file, k);
9715 if (arith ==
"real" || arith ==
"real:32")
9716 return solve_model_nc_busyp<line::Real<32> >(file, k);
9717 if (arith ==
"real:64")
return solve_model_nc_busyp<line::Real<64> >(file, k);
9718 if (arith ==
"real:128")
return solve_model_nc_busyp<line::Real<128> >(file, k);
9719 if (arith ==
"real:256")
return solve_model_nc_busyp<line::Real<256> >(file, k);
9720 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9722 if (analysis ==
"sysmarg") {
9723 if (arith ==
"double")
return solve_model_nc_sysmarg<double>(file, k);
9724 if (arith ==
"exact")
return solve_model_nc_sysmarg<line::Rational>(file, k);
9725 if (arith ==
"real:16")
return solve_model_nc_sysmarg<line::Real<16> >(file, k);
9726 if (arith ==
"real" || arith ==
"real:32")
9727 return solve_model_nc_sysmarg<line::Real<32> >(file, k);
9728 if (arith ==
"real:64")
return solve_model_nc_sysmarg<line::Real<64> >(file, k);
9729 if (arith ==
"real:128")
return solve_model_nc_sysmarg<line::Real<128> >(file, k);
9730 if (arith ==
"real:256")
return solve_model_nc_sysmarg<line::Real<256> >(file, k);
9731 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9733 if (analysis ==
"marg") {
9734 if (arith ==
"double")
return solve_model_nc_marg<double>(file, k);
9735 if (arith ==
"exact")
return solve_model_nc_marg<line::Rational>(file, k);
9736 if (arith ==
"real:16")
return solve_model_nc_marg<line::Real<16> >(file, k);
9737 if (arith ==
"real" || arith ==
"real:32")
9738 return solve_model_nc_marg<line::Real<32> >(file, k);
9739 if (arith ==
"real:64")
return solve_model_nc_marg<line::Real<64> >(file, k);
9740 if (arith ==
"real:128")
return solve_model_nc_marg<line::Real<128> >(file, k);
9741 if (arith ==
"real:256")
return solve_model_nc_marg<line::Real<256> >(file, k);
9742 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9746 if (analysis ==
"normconst") {
9747 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
9748 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
9749 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
9750 if (arith ==
"real" || arith ==
"real:32")
9751 return solve_model_normconst<line::Real<32> >(file, k, s);
9752 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
9753 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
9754 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
9755 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9757 if (analysis ==
"sens") {
9758 if (arith ==
"double")
return solve_model_nc_sens<double>(file, k);
9759 if (arith ==
"exact")
return solve_model_nc_sens<line::Rational>(file, k);
9760 if (arith ==
"real:16")
return solve_model_nc_sens<line::Real<16> >(file, k);
9761 if (arith ==
"real" || arith ==
"real:32")
9762 return solve_model_nc_sens<line::Real<32> >(file, k);
9763 if (arith ==
"real:64")
return solve_model_nc_sens<line::Real<64> >(file, k);
9764 if (arith ==
"real:128")
return solve_model_nc_sens<line::Real<128> >(file, k);
9765 if (arith ==
"real:256")
return solve_model_nc_sens<line::Real<256> >(file, k);
9766 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9768 if (analysis ==
"cdf") {
9769 if (arith ==
"double")
return solve_model_nc_cdf<double>(file, k);
9770 if (arith ==
"exact")
return solve_model_nc_cdf<line::Rational>(file, k);
9771 if (arith ==
"real:16")
return solve_model_nc_cdf<line::Real<16> >(file, k);
9772 if (arith ==
"real" || arith ==
"real:32")
9773 return solve_model_nc_cdf<line::Real<32> >(file, k);
9774 if (arith ==
"real:64")
return solve_model_nc_cdf<line::Real<64> >(file, k);
9775 if (arith ==
"real:128")
return solve_model_nc_cdf<line::Real<128> >(file, k);
9776 if (arith ==
"real:256")
return solve_model_nc_cdf<line::Real<256> >(file, k);
9777 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9779 if (analysis ==
"prob") {
9780 if (arith ==
"double")
return solve_model_nc_prob<double>(file, k);
9781 if (arith ==
"exact")
return solve_model_nc_prob<line::Rational>(file, k);
9782 if (arith ==
"real:16")
return solve_model_nc_prob<line::Real<16> >(file, k);
9783 if (arith ==
"real" || arith ==
"real:32")
9784 return solve_model_nc_prob<line::Real<32> >(file, k);
9785 if (arith ==
"real:64")
return solve_model_nc_prob<line::Real<64> >(file, k);
9786 if (arith ==
"real:128")
return solve_model_nc_prob<line::Real<128> >(file, k);
9787 if (arith ==
"real:256")
return solve_model_nc_prob<line::Real<256> >(file, k);
9788 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9790 if (arith ==
"double")
return solve_model_nc<double>(file, k);
9791 if (arith ==
"exact")
return solve_model_nc<line::Rational>(file, k);
9792 if (arith ==
"real:16")
return solve_model_nc<line::Real<16> >(file, k);
9793 if (arith ==
"real" || arith ==
"real:32")
return solve_model_nc<line::Real<32> >(file, k);
9794 if (arith ==
"real:64")
return solve_model_nc<line::Real<64> >(file, k);
9795 if (arith ==
"real:128")
return solve_model_nc<line::Real<128> >(file, k);
9796 if (arith ==
"real:256")
return solve_model_nc<line::Real<256> >(file, k);
9797 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9805 if (analysis !=
"avg" && analysis !=
"cdf")
9807 "the AG solver ports -a avg (with its views -a node, -a sys, -a chain and "
9808 "-a nodechain) and -a cdf, the inherited exponential fallback: RCAT converges a "
9809 "fixed point over the synchronization rates and reports mean measures, forming no "
9810 "state probability or transient (got '" + analysis +
"')");
9811 if (analysis ==
"cdf") {
9812 if (arith ==
"double")
return solve_model_ag_cdf<double>(file, k);
9813 if (arith ==
"exact")
return solve_model_ag_cdf<line::Rational>(file, k);
9814 if (arith ==
"real:16")
return solve_model_ag_cdf<line::Real<16> >(file, k);
9815 if (arith ==
"real" || arith ==
"real:32")
9816 return solve_model_ag_cdf<line::Real<32> >(file, k);
9817 if (arith ==
"real:64")
return solve_model_ag_cdf<line::Real<64> >(file, k);
9818 if (arith ==
"real:128")
return solve_model_ag_cdf<line::Real<128> >(file, k);
9819 if (arith ==
"real:256")
return solve_model_ag_cdf<line::Real<256> >(file, k);
9820 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9822 if (arith ==
"double")
return solve_model_ag<double>(file, k);
9823 if (arith ==
"exact")
return solve_model_ag<line::Rational>(file, k);
9824 if (arith ==
"real:16")
return solve_model_ag<line::Real<16> >(file, k);
9825 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ag<line::Real<32> >(file, k);
9826 if (arith ==
"real:64")
return solve_model_ag<line::Real<64> >(file, k);
9827 if (arith ==
"real:128")
return solve_model_ag<line::Real<128> >(file, k);
9828 if (arith ==
"real:256")
return solve_model_ag<line::Real<256> >(file, k);
9829 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9832 if (analysis !=
"avg" && analysis !=
"bounds" && analysis !=
"cdf")
9834 "-a cdf, the inherited exponential fallback (got '" +
9836 if (analysis ==
"cdf") {
9837 if (arith ==
"double")
return solve_model_ba_cdf<double>(file, k);
9838 if (arith ==
"exact")
return solve_model_ba_cdf<line::Rational>(file, k);
9839 if (arith ==
"real:16")
return solve_model_ba_cdf<line::Real<16> >(file, k);
9840 if (arith ==
"real" || arith ==
"real:32")
9841 return solve_model_ba_cdf<line::Real<32> >(file, k);
9842 if (arith ==
"real:64")
return solve_model_ba_cdf<line::Real<64> >(file, k);
9843 if (arith ==
"real:128")
return solve_model_ba_cdf<line::Real<128> >(file, k);
9844 if (arith ==
"real:256")
return solve_model_ba_cdf<line::Real<256> >(file, k);
9845 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9847 if (analysis ==
"bounds") {
9848 if (arith ==
"double")
return solve_model_ba_bounds<double>(file, k);
9849 if (arith ==
"exact")
return solve_model_ba_bounds<line::Rational>(file, k);
9850 if (arith ==
"real:16")
return solve_model_ba_bounds<line::Real<16> >(file, k);
9851 if (arith ==
"real" || arith ==
"real:32")
9852 return solve_model_ba_bounds<line::Real<32> >(file, k);
9853 if (arith ==
"real:64")
return solve_model_ba_bounds<line::Real<64> >(file, k);
9854 if (arith ==
"real:128")
return solve_model_ba_bounds<line::Real<128> >(file, k);
9855 if (arith ==
"real:256")
return solve_model_ba_bounds<line::Real<256> >(file, k);
9856 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9858 if (arith ==
"double")
return solve_model_ba<double>(file, k);
9859 if (arith ==
"exact")
return solve_model_ba<line::Rational>(file, k);
9860 if (arith ==
"real:16")
return solve_model_ba<line::Real<16> >(file, k);
9861 if (arith ==
"real" || arith ==
"real:32")
return solve_model_ba<line::Real<32> >(file, k);
9862 if (arith ==
"real:64")
return solve_model_ba<line::Real<64> >(file, k);
9863 if (arith ==
"real:128")
return solve_model_ba<line::Real<128> >(file, k);
9864 if (arith ==
"real:256")
return solve_model_ba<line::Real<256> >(file, k);
9865 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9868 if (analysis !=
"avg" && analysis !=
"cdf")
9870 "SolverLQNS on a Network reports -a avg and -a cdf, the inherited exponential "
9872 "qnsolver returns one chain-level table of means and computes no state "
9873 "probability and no transient (got '" + analysis +
"')");
9877 if (arith !=
"double")
9879 "SolverLQNS reads its results back as the decimal text an external binary printed, "
9880 "which is double at best; --arith " + arith +
9881 " would report a precision the tool never produced");
9882 if (analysis ==
"cdf")
return solve_model_lqns_network_cdf(file, k);
9883 return solve_model_lqns_network<double>(file, k);
9885 if (s ==
"fluid" || s ==
"fld") {
9886 if (analysis !=
"avg" && analysis !=
"odes" && analysis !=
"var" &&
9887 analysis !=
"tranvar" && analysis !=
"jacobian" && analysis !=
"tran" &&
9888 analysis !=
"prob" && analysis !=
"cdf" && analysis !=
"aoi" &&
9889 analysis !=
"statevec")
9891 "the fluid solver ports -a avg, -a tran, -a tranvar, -a prob, -a cdf, -a aoi, "
9892 "-a odes, -a statevec, -a var and -a jacobian (got '" + analysis +
"')");
9895 if (arith !=
"double")
9897 "the fluid solver integrates its drift with LSODA, which is double precision by "
9898 "construction; rerun with --arith double (got '" + arith +
"')");
9899 if (analysis ==
"odes")
return solve_model_fluid_odes<double>(file, k);
9900 if (analysis ==
"statevec")
return solve_model_fluid_statevec<double>(file, k);
9901 if (analysis ==
"jacobian")
return solve_model_fluid_jacobian<double>(file, k);
9902 if (analysis ==
"var")
return solve_model_fluid_var<double>(file, k);
9903 if (analysis ==
"tranvar")
return solve_model_fluid_tranvar<double>(file, k);
9904 if (analysis ==
"tran")
return solve_model_fluid_tran<double>(file, k);
9905 if (analysis ==
"prob")
return solve_model_fluid_prob<double>(file, k);
9906 if (analysis ==
"cdf")
return solve_model_fluid_cdf<double>(file, k);
9907 if (analysis ==
"aoi")
return solve_model_fluid_aoi<double>(file, k);
9908 return solve_model_fluid<double>(file, k);
9910 if (analysis ==
"prob") {
9911 if (arith ==
"double")
return solve_model_prob<double>(file, k);
9912 if (arith ==
"exact")
return solve_model_prob<line::Rational>(file, k);
9913 if (arith ==
"real:16")
return solve_model_prob<line::Real<16> >(file, k);
9914 if (arith ==
"real" || arith ==
"real:32")
return solve_model_prob<line::Real<32> >(file, k);
9915 if (arith ==
"real:64")
return solve_model_prob<line::Real<64> >(file, k);
9916 if (arith ==
"real:128")
return solve_model_prob<line::Real<128> >(file, k);
9917 if (arith ==
"real:256")
return solve_model_prob<line::Real<256> >(file, k);
9918 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9920 if (analysis ==
"marg") {
9921 if (arith ==
"double")
return solve_model_marg<double>(file, k);
9922 if (arith ==
"exact")
return solve_model_marg<line::Rational>(file, k);
9923 if (arith ==
"real:16")
return solve_model_marg<line::Real<16> >(file, k);
9924 if (arith ==
"real" || arith ==
"real:32")
return solve_model_marg<line::Real<32> >(file, k);
9925 if (arith ==
"real:64")
return solve_model_marg<line::Real<64> >(file, k);
9926 if (arith ==
"real:128")
return solve_model_marg<line::Real<128> >(file, k);
9927 if (arith ==
"real:256")
return solve_model_marg<line::Real<256> >(file, k);
9928 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9930 if (analysis ==
"normconst") {
9931 if (arith ==
"double")
return solve_model_normconst<double>(file, k, s);
9932 if (arith ==
"exact")
return solve_model_normconst<line::Rational>(file, k, s);
9933 if (arith ==
"real:16")
return solve_model_normconst<line::Real<16> >(file, k, s);
9934 if (arith ==
"real" || arith ==
"real:32")
9935 return solve_model_normconst<line::Real<32> >(file, k, s);
9936 if (arith ==
"real:64")
return solve_model_normconst<line::Real<64> >(file, k, s);
9937 if (arith ==
"real:128")
return solve_model_normconst<line::Real<128> >(file, k, s);
9938 if (arith ==
"real:256")
return solve_model_normconst<line::Real<256> >(file, k, s);
9939 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9941 if (analysis ==
"cdf") {
9944 if (arith ==
"double")
return solve_model_mva_cdf<double>(file, k);
9945 if (arith ==
"exact")
return solve_model_mva_cdf<line::Rational>(file, k);
9946 if (arith ==
"real:16")
return solve_model_mva_cdf<line::Real<16> >(file, k);
9947 if (arith ==
"real" || arith ==
"real:32")
9948 return solve_model_mva_cdf<line::Real<32> >(file, k);
9949 if (arith ==
"real:64")
return solve_model_mva_cdf<line::Real<64> >(file, k);
9950 if (arith ==
"real:128")
return solve_model_mva_cdf<line::Real<128> >(file, k);
9951 if (arith ==
"real:256")
return solve_model_mva_cdf<line::Real<256> >(file, k);
9952 throw line::InputError(
"--arith '" + arith +
"' is not a model-solve backend");
9954 if (analysis !=
"avg")
9956 "the model-solving path ports -a avg, -a node, -a prob, -a marg, -a normconst and "
9964 if (arith ==
"double")
return solve_model_mva<double>(file, k);
9965 if (arith ==
"exact")
return solve_model_mva<line::Rational>(file, k);
9966 if (arith ==
"real:16")
return solve_model_mva<line::Real<16> >(file, k);
9967 if (arith ==
"real" || arith ==
"real:32")
return solve_model_mva<line::Real<32> >(file, k);
9968 if (arith ==
"real:64")
return solve_model_mva<line::Real<64> >(file, k);
9969 if (arith ==
"real:128")
return solve_model_mva<line::Real<128> >(file, k);
9970 if (arith ==
"real:256")
return solve_model_mva<line::Real<256> >(file, k);
9972 "--arith '" + arith +
9973 "' is not a model-solve backend; use double, exact or real:<16|32|64|128|256>");
9983void print_brief_help() {
9985 "LINE solver (C++), version %s\n"
9987 "Usage: line-cli -f <model> [-s <solver>] [-a <analysis>] [-o <format>]\n"
9988 " cat model.json | line-cli\n"
9991 " -f, --file <path> model file: .json (network), .lqnx (layered),\n"
9992 " .jsimg (JMT), .pnml (Petri net); stdin if omitted\n"
9993 " -s, --solver <name> auto (default), mva, nc, ctmc, mam, fluid, ssa,\n"
9994 " ldes, jmt, ag, ba, uq; ln for layered models, env\n"
9995 " for random environments\n"
9996 " -a, --analysis <type> avg (default), node, sys, chain, tran, prob, cdf,\n"
9997 " states, sample, normconst, bounds, ... (comma list)\n"
9998 " -o, --output <fmt> readable (default) | json | jsimg (export the\n"
9999 " model as a JMT simulation document)\n"
10000 " --method <name> algorithm within the chosen solver\n"
10001 " --samples <n> simulation run length (ssa, ldes); default 10000\n"
10002 " --seed <n> random seed; default 23000\n"
10003 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
10004 " the solver console, a running progress log\n"
10005 " --find-solver [m] which solvers and methods can analyze this model,\n"
10006 " optionally only those answering measure <m>\n"
10007 " (avg, tran, cdf, prob, sample, ...); reports and exits\n"
10008 " --find-solver-all [m] the same, keeping the refused pairs and the\n"
10009 " reason each was refused\n"
10010 " -h, --help this message\n"
10011 " --help-all every option, solver by solver\n"
10012 " -V, --version version string\n"
10013 " --install environment check: which optional backends\n"
10014 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
10017 " line-cli -f model.json solve, letting auto pick the solver\n"
10018 " line-cli -f model.json -s mva -a avg mean queue lengths, MVA\n"
10019 " line-cli -f model.lqnx -s ln solve a layered model\n"
10020 " line-cli -f model.json -s ssa --samples 1e6 -o json\n"
10021 " line-cli -f model.json --find-solver what can solve this model\n"
10022 " line-cli -f model.json --find-solver cdf ... and return a passage-time law\n"
10024 "Each solver has flags of its own (tolerances, horizons, engine choices):\n"
10025 "run `line-cli --help-all` for the full reference.\n",
10031 "LINE multiprecision solver (C++), version %s\n"
10033 "Usage: line-cli [OPTIONS]\n"
10034 " cat model.json | line-cli -i json [OPTIONS]\n"
10035 " line-cli model.lqnx [OPTIONS]\n"
10037 "Options (flag-compatible with jline.cli.LineCLI):\n"
10038 " -f, --file <path> model file; stdin when omitted (json only)\n"
10039 " -i, --input <fmt> input format: json | jsim | jsimg | jsimw |\n"
10040 " lqnx | xml | pnml. Without it a .lqnx or .xml\n"
10041 " path is read as a layered model, a\n"
10042 " .jsim/.jsimg/.jsimw path as a JMT simulation\n"
10043 " document, a .pnml path as a place/transition\n"
10044 " net (ISO/IEC 15909-2), and anything else as a\n"
10045 " Network model.json. The three jsim spellings\n"
10046 " name ONE format, as they do in JMT\n"
10047 " -o, --output <fmt> output format: readable | json | jsimg\n"
10048 " (| layers, lqnx for layered models).\n"
10049 " jsimg exports the model as a JMT simulation\n"
10050 " document and solves nothing -- the write half\n"
10051 " of -i jsim|jsimg|jsimw, and the same three\n"
10052 " spellings are accepted. --seed, --samples and\n"
10053 " --tspan set the controls its header carries\n"
10054 " json is honoured by EVERY analysis, not only\n"
10055 " -a avg: each answers under a key named after\n"
10056 " its -a, with the arithmetic and the resolved\n"
10057 " method beside it, and every index inside a\n"
10058 " payload is 0-based against the tables' 1-based\n"
10059 " columns (each payload states its indexBase)\n"
10060 " -s, --solver <name> Network: auto, mva, nc, ctmc, mam, ba, ssa, fluid, uq,\n"
10061 " ldes (the SSJ discrete-event engine, run as a\n"
10062 " subprocess on common/ldes or common/ldes.jar),\n"
10063 " jmt (the Java Modelling Tools engine),\n"
10064 " lqns (its qns methods, the external qnsolver\n"
10066 " layered: auto, ln, ln.mva, ln.comom, lqns,\n"
10067 " ldes (the native in-process LN simulator, a\n"
10068 " sample path of the layered model itself and\n"
10069 " not a decomposition into layers; it takes\n"
10070 " --samples and --seed, and NOT the --ldes-*\n"
10071 " family, which configures the subprocess\n"
10072 " engine that answers -s ldes on a Network)\n"
10073 " environment: env (an Environment model.json)\n"
10074 " -a, --analysis <type> analysis: avg, node, sys, chain, nodechain,\n"
10075 " stage, cache, item, prob, marg, cdf, cdf-passt,\n"
10076 " perct-respt, tran, tran-cdf-respt,\n"
10077 " tran-cdf-passt, tranprob, tranreward,\n"
10078 " reward-value, normconst, gen, states, sample,\n"
10079 " reward, sens, first-passt, odes, statevec,\n"
10080 " var, tranvar, busyperiod,\n"
10081 " jacobian, aoi, internals, bounds, posterior,\n"
10082 " interval. A COMMA LIST runs several in order\n"
10083 " (`-a avg,sys`), emitting one -o json envelope\n"
10084 " per analysis rather than one merged object.\n"
10085 " The JAR CLI's own spellings are accepted as\n"
10086 " aliases -- cdf-respt, prob-sys-aggr, tran-avg,\n"
10087 " generator, reward-steady, all -- and collapse\n"
10088 " onto the arm that already answers them whole:\n"
10089 " -a prob reports getProbSys, getProbSysAggr and\n"
10090 " the per-station pair together, -a sample walks\n"
10091 " all four samplers at once, and -a stage IS\n"
10092 " -a avg on a Network (one implicit stage).\n"
10094 " solver serves is stated by its own refusal;\n"
10095 " ssa serves avg, prob and sample (prob and\n"
10096 " sample run the SERIAL engine whatever -m\n"
10097 " said, since the NRM simulates counts rather\n"
10098 " than the state encoding)\n"
10099 " -v, --verbosity <lvl> silent | standard | debug; debug turns on\n"
10100 " the solver console, a running progress log\n"
10101 " of every solver run\n"
10102 " -d, --seed <n> random seed (SSA, nc --method cftp); default\n"
10103 " 23000. -d is the JAR CLI's spelling\n"
10104 " --warmupfrac <f> SSA: leading fraction of the path discarded\n"
10105 " before the means are taken, in [0,1)\n"
10106 " --pstar <p> fluid: exponent of the p-norm smoothing of the\n"
10107 " drift; without it the hard min() is integrated\n"
10108 " --busyperiod <n,..> -a busyperiod: the orders wanted; default 1\n"
10109 " --busyperiod-subnet <i,..>\n"
10110 " -a busyperiod: the 1-based stations forming the\n"
10111 " subnetwork. Required -- a busy period is defined\n"
10112 " for a NAMED set and no default can choose one\n"
10113 " --method <name> algorithm within the solver\n"
10114 " --samples <n> simulation run length (SSA); default 10000.\n"
10115 " Accepts 1e6 as well as 1000000. A simulation\n"
10116 " figure is only a measurement WITH this number,\n"
10117 " which is why the SSA banner reports it back.\n"
10118 " Also the draw count of nc --method cftp,\n"
10119 " default 1e5, where the draw is the answer.\n"
10120 " --mdd-tol <x> level-iteration tolerance of ctmc --method mdd;\n"
10121 " default 1e-12. NOT --tol: that iteration is an\n"
10122 " inner solve whose fixed point is checked\n"
10123 " against the population invariant at 1e-6, so a\n"
10124 " solver-sized tolerance stops short of it\n"
10125 " --mdd-maxiter <n> coupled sweeps before ctmc --method mdd is\n"
10126 " declared non-convergent; default 500\n"
10127 " --level <n> hierarchy level of the ba pbh/bjbh/cbh/sib\n"
10128 " families; default 2\n"
10129 " --qrf-params <j> JSON (inline or a path) with the QRF blocking\n"
10130 " tables of -s ba --method qrf.bas|qrf.rsrd:\n"
10131 " f, MR, BB, MM, MM1, ZZ and optionally F, the\n"
10132 " fields sn_to_qrf_params assembles. ZM is\n"
10133 " derived from ZZ. There is no default: assuming\n"
10134 " no blocking puts the bound ~31x farther from\n"
10135 " exact, so its absence is refused\n"
10136 " --qrf-alpha <j> JSON (nstations x N) load-dependent scaling of\n"
10137 " the ba qrf.mmi.ld, qrf.mmi.linear and qrf.rsrd\n"
10138 " arms; default all ones\n"
10139 " --tol <x> convergence tolerance (mva, nc, mam, ag, fluid)\n"
10140 " --iter_tol <x> outer-loop tolerance (mva, nc, fluid)\n"
10141 " --iter_max <n> iteration cap (mva, nc, mam, ag, fluid)\n"
10142 " --max-states <n> truncation level of an OPEN agent's queue-length\n"
10143 " dimension (ag), options.config.maxStates;\n"
10144 " default 100. A closed class is bounded by its\n"
10145 " own population instead, and --method inapinf\n"
10146 " ignores the level and solves the open agents on\n"
10147 " the infinite state space\n"
10148 " --fork-join <arm> which fork-join transform the mean-value fixed\n"
10149 " point takes (mva, nc): default|mmt|fjt is the\n"
10150 " MMT transform, ht|heidelberger-trivedi the\n"
10151 " Heidelberger-Trivedi one, which is CLOSED\n"
10152 " models only and a different answer to the same\n"
10153 " model rather than a faster route to one\n"
10154 " --cutoff <n|matrix> open jobs per class in the CTMC state space;\n"
10155 " a matrix is per (station,class), '1,1,0;3,3,0;0,0,3'\n"
10156 " (ctmc); without it the reference's\n"
10157 " ceil(6000^(1/(M*K))) is used and reported.\n"
10158 " Also the level truncation of mam -a prob,\n"
10159 " whose open queue has no bound of its own\n"
10160 " --fj-accuracy <n> FJ_codes truncation C of the queue-length\n"
10161 " difference between the two fork-join\n"
10162 " branches (mam, homogeneous fork-join);\n"
10163 " default 100, larger is more accurate\n"
10164 " --fj-tmode <mode> how that approximation solves for its T\n"
10165 " matrix: NARE (default) or Sylves\n"
10166 " --timescale <mode> auto (default), discrete or continuous: how\n"
10167 " -s mam reads the time scale. auto lets the\n"
10168 " distributions decide; discrete raises rather\n"
10169 " than solve a model that mixes lattice and\n"
10170 " non-lattice laws. The slot is --slotlength\n"
10171 " --tspan <t0>:<t1> transient horizon (ctmc -a tranprob,\n"
10172 " mam -a tran, fluid); a bare <t1> starts at 0.\n"
10173 " For the CTMC and MAM there is no default:\n"
10174 " pi(t) on an unstated horizon is not a\n"
10175 " quantity. For the fluid solver it bounds the\n"
10176 " integration, and is what -s fluid --method kp\n"
10177 " reports its covariance AT\n"
10178 " -n, --node <n> 1-based stateful node a state query is\n"
10179 " labelled by (ctmc -a tranprob, -a sample and\n"
10180 " ssa -a sample), the\n"
10181 " queue mam -a prob reports, or the station\n"
10182 " mva -a marg reports; without it the\n"
10183 " whole network is reported (the MAM queries\n"
10184 " take the model's only Queue)\n"
10185 " -c, --class <r> 1-based job class of mva -a marg; without it\n"
10186 " every class is reported\n"
10187 " NOTE ON THE INDEX BASE: -n and -c are 1-BASED\n"
10188 " here, as every station index this CLI takes\n"
10189 " is, and 0-BASED in jline.cli.LineCLI, which\n"
10190 " indexes as Java does. The short spellings are\n"
10191 " accepted so one command line parses in both,\n"
10192 " but the SAME number names a different node --\n"
10193 " the two bridges (cpp_dispatch, jar_dispatch)\n"
10194 " each convert for their own CLI\n"
10195 " --marg-states <ns> comma-separated job counts the mva -a marg\n"
10196 " curve is evaluated at, the reference's\n"
10197 " state_m; without it each law takes its own\n"
10198 " default range (0..N_r closed, mean + 5 sigma\n"
10199 " Poisson, the 1e-10 tail geometric)\n"
10200 " --notation <form> scalar (default) | matrix, the form the ODE\n"
10201 " export writes (fluid -a odes only)\n"
10202 " --symbolic <b> computer-algebra backend of fluid -a\n"
10203 " jacobian: auto (default, searches for a\n"
10204 " line-sage-rest service), a URL, an image\n"
10205 " name, or none to differentiate locally\n"
10206 " --equilibria also ask the backend for the solutions of\n"
10207 " f(x) = 0 (fluid -a jacobian). Needs a\n"
10208 " backend: solving is not differentiating\n"
10209 " --cdf-algorithm <a> exact (default, pfqn_stdf) | rd\n"
10210 " (pfqn_stdf_heur), how the sojourn law is\n"
10211 " inverted (nc -a cdf only)\n"
10212 " --perm-engine <e> exact (default, Ryser) | spm | bethe | heur |\n"
10213 " huberlaw | adapart, the permanent estimator\n"
10214 " of nc -a sysmarg. The five approximations\n"
10215 " refuse a demand matrix with a zero entry;\n"
10216 " spm is the saddle point, whose cost does not\n"
10217 " grow with the population\n"
10218 " --tran-points <n> points on the uniform transient grid the ENV\n"
10219 " mean-field coupling sums its stage exit\n"
10220 " metrics over (env only); default 1001\n"
10221 " --state <n,...> the ENCODED state row of the node named by\n"
10222 " --node that -a prob asks about, i.e.\n"
10223 " getProb(node, state)'s second argument; without\n"
10224 " it the query is about the model's default\n"
10226 " --events <n> length of ONE sampled trajectory (-a sample);\n"
10227 " default 1000. NOT --samples, which is a\n"
10228 " solver's run length\n"
10229 " --timestep <dt> fixed output step of a transient CTMC solve\n"
10230 " (-a tranprob, -a tranreward), options.timestep\n"
10231 " of ctmc_transient.m; without it the grid is the\n"
10232 " integrator's own adaptive one. It changes WHERE\n"
10233 " the solution is reported, not how it is\n"
10234 " computed: the grid points are read off the same\n"
10236 " --transient-method <m> ode (default) or fau, how the CTMC forward\n"
10237 " equation is advanced over the output grid\n"
10238 " --fau-epsilon <e> fau: probability mass the grid may discard\n"
10239 " --fau-delta <d> fau: occupancy below which a state is dropped\n"
10240 " --rate-sched <j> -s ctmc -a tran: time-varying rates, a JSON array\n"
10241 " (inline or a file) of {station, class, tgrid,\n"
10242 " rates[, nominal]}; station and class by name or\n"
10243 " 1-based index. The rate of that pair follows the\n"
10244 " piecewise-linear schedule, held at its end values\n"
10245 " outside tgrid (options.config.rate_sched)\n"
10246 " --ctmc-tv-ngrid <n> grid size of the --rate-sched propagator (100)\n"
10247 " --percentiles <p,..> levels getPerctRespT is read at (-s mam),\n"
10248 " as fractions (0.9) or percents (90); default\n"
10249 " 0.50,0.90,0.95,0.99, the reference's pers_stored\n"
10250 " --reward-name <nm> which declared reward -a reward-value returns\n"
10251 " the value function of. REQUIRED there and never\n"
10252 " defaulted: two rewards have different value\n"
10253 " functions, and picking one would mislabel it\n"
10254 " --map-env <mode> default (on) | off, whether a model whose ONLY\n"
10255 " unsupported features are MAP/MMPP2/MMAP/MPH is\n"
10256 " solved through a random-environment image of the\n"
10257 " modulating chain instead of being refused. Not a\n"
10258 " -s env knob: it applies to mva, nc and fluid,\n"
10259 " which is where the refusal it replaces comes\n"
10260 " from. off restores that plain refusal\n"
10261 " --map-env-method <m> auto (default) | dec | avg | meanfield, the\n"
10262 " recombination the image is solved with. auto\n"
10263 " picks from the environment timescale; meanfield\n"
10264 " needs a stage backend and is refused by name\n"
10265 " where there is none\n"
10266 " --map-env-maxstages <n> cap on the stages map2renv draws from the\n"
10267 " modulating chain; 0, the default, takes every\n"
10268 " phase. Beyond the JAR, which has no such cap\n"
10269 " -p, --port <n> run as a solve SERVER on this port, speaking\n"
10270 " LineWebSocketServer's protocol: one WebSocket\n"
10271 " text message per connection, its first line the\n"
10272 " comma-separated argument list and its remainder\n"
10273 " the model document; the CLI's output comes back\n"
10274 " as one text message. Plaintext, one connection\n"
10275 " at a time, and -f is refused beside it\n"
10276 " -m, --maxreq <n> quit after serving n requests; without it the\n"
10277 " server runs until interrupted\n"
10278 " -h, --help the short message: the flags a first run needs\n"
10279 " --help-all this message, every option solver by solver\n"
10280 " -V, --version version string\n"
10281 " --install environment check: report which optional backends\n"
10282 " (Java/JMT, LQNS, qnsolver, SageMath) are reachable\n"
10284 "Layered models (-i lqnx|xml) additionally take:\n"
10285 " --layer-solver <s> solver run in each layer: mva (default)|nc|\n"
10286 " fluid|ssa, the C++ spelling of the reference's\n"
10287 " factory argument: LN(model, @(m) MVA(m))\n"
10288 " against LN(model, @(m) Fluid(m)). They converge\n"
10289 " to DIFFERENT fixed points, not to the same one\n"
10290 " by different routes, because each layer's\n"
10291 " results feed the next outer iteration's demands.\n"
10292 " fluid is double only and refuses a layer with a\n"
10293 " fork; ssa is NOISY, so the outer loop switches\n"
10294 " to the Robbins-Monro / Polyak-Ruppert controller\n"
10295 " --method <name> the LN update: default | moment3 | mw.upper |\n"
10296 " mw.lower. moment3 fits an APH to each layer's\n"
10297 " response-time CDF and convolves along the entry,\n"
10298 " which is what makes -a cdf possible; mw.*\n"
10299 " reports Majumdar-Woodside box BOUNDS on\n"
10300 " throughput and processor utilization and solves\n"
10301 " no layer at all (every other metric is NaN)\n"
10302 " --ln-transient <m> coupled (default) | decoupled, how -a tran\n"
10303 " couples the layers. decoupled freezes the\n"
10304 " inter-layer demands at the fixed point; coupled\n"
10305 " relaxes time-varying demands through the fluid\n"
10306 " rate schedule until the trajectories settle\n"
10307 " --ln-transient-channels <c> both (default) | thinkt | callservt,\n"
10308 " which inter-layer coupling the relaxation\n"
10309 " injects, for isolating one channel's share\n"
10310 " --sens-method <m> auto (default) | exact | fd, the branch each\n"
10311 " LAYER's sensitivity table takes (-a sens);\n"
10312 " the same three under -s nc -a sens\n"
10313 " --sens-scheme <s> forward (default) | central, the difference\n"
10314 " quotient of the fd branch\n"
10315 " --sens-step <h> relative rate perturbation of the fd branch,\n"
10316 " in (0,1); default 1e-4, or 1e-2 for ssa layers\n"
10317 " --no-interlocking disable the interlocking correction\n"
10318 " --interlock-method <m> ilrate (default) | refpath | none, how\n"
10319 " the interlock is tracked; refpath merges the\n"
10320 " callers that are one reference task's\n"
10321 " customers into one chain (srvn.cs only, it\n"
10322 " falls back to ilrate elsewhere, with a warning)\n"
10323 " --interlock-maxpaths <n> refpath refuses a layer carrying more\n"
10324 " reference routes into it; default 32\n"
10325 " --interlock-refpath-scope <s> merging (default) | all, whether\n"
10326 " refpath also rewrites a lone-caller layer\n"
10327 " --repeat <k> re-solve k times, report the best wall clock\n"
10328 " -o layers dump every layer's stations, classes, rates\n"
10329 " and routing instead of the AvgTable\n"
10330 " -a takes avg (getAvgTable), tran (getTranAvg, needs --tspan and fluid\n"
10331 " layers), sens (getSensitivityTable) and cdf (getCdfRespT, moment3).\n"
10332 " --iter_max and --iter_tol set the outer LN loop; --tol does not apply,\n"
10333 " and neither does any Network solver token.\n"
10335 "The external LQNS binary (-s lqns) additionally takes:\n"
10336 " --method <name> default | lqns | srvn | exactmva |\n"
10337 " srvn.exactmva | sim | lqsim | lqns.default.\n"
10338 " sim and lqsim run lqsim, the SIMULATOR;\n"
10339 " lqns.default is lqns with no pragma at all,\n"
10340 " which is a different fixed point and not a\n"
10341 " synonym for default\n"
10342 " --multiserver <p> conway|rolia|zhou|suri|reiser|schmidt|default\n"
10343 " (= rolia), the -Pmultiserver= pragma. Not\n"
10344 " passed to lqsim, which has no MVA to configure\n"
10345 " --samples <n> lqsim run length (-A); default 10000\n"
10346 " --timeout <s> kill the child after s seconds; without it the\n"
10348 " --keep keep the working directory with model.lqnx and\n"
10349 " model.lqxo instead of removing it\n"
10350 " --verbose echo the command line and the binary's output\n"
10351 " --remote[-url <u>] solve on a host running lqns-rest instead of\n"
10352 " locally; -url implies --remote. LINE ships no\n"
10353 " LQNS binary, so this is the other way to reach\n"
10355 " -a takes avg only: lqns computes no transient, no sensitivity and no\n"
10356 " response-time distribution. QLen is the element utilization, Util its\n"
10357 " processor utilization per server, RespT its phase-1 service time;\n"
10358 " ResidT and ArvR print NaN because lqns reports neither.\n"
10360 "The external qnsolver binary (-s lqns on a Network) additionally takes:\n"
10361 " --method <name> default | qns (both = rolia) | qns.conway |\n"
10362 " qns.rolia | qns.zhou | qns.reiser | qns.suri |\n"
10363 " qns.schmidt. The multiserver approximation,\n"
10364 " passed as qnsolver -m and only\n"
10365 " when the model HAS a multiserver station. suri\n"
10366 " and schmidt are lqns approximations: they reach\n"
10367 " the tool only on the non-product-form closed\n"
10368 " branch below, and a multiserver model on the\n"
10369 " qnsolver branch refuses them by name\n"
10370 " --multiserver <p> the same choice under its config spelling;\n"
10371 " --method wins when it names one\n"
10372 " --timeout <s> kill the child after s seconds; without it the\n"
10374 " --keep keep the working directory with model.jmva and\n"
10375 " result.jmva instead of removing it\n"
10376 " -a takes avg only. The model is marshalled to the JMVA interchange\n"
10377 " format at CHAIN level and the chain results are de-aggregated back to\n"
10378 " classes, so only Queue, Delay and Source stations are expressible; any\n"
10379 " other station is refused rather than dropped. A closed model that is\n"
10380 " NOT product-form is converted with QN2LQN and delegated to lqns, as the\n"
10381 " reference does, so it also needs the lqns binary; class priorities are\n"
10382 " outside that conversion and are refused. --arith is double only, since\n"
10383 " the results arrive as the text an external binary printed.\n"
10385 "The Java Modelling Tools wrapper (-s jmt) additionally takes:\n"
10386 " --jmt-replications <n> independent JSIM runs the transient ensemble\n"
10387 " of -a tran and -a tranprob averages over\n"
10388 " (options.config.replications, default 10),\n"
10389 " replication k seeded seed+k-1. A single path\n"
10390 " is NOT E[N](t); --method jsim is one run\n"
10392 "Discrete-event simulation (-s ldes) additionally takes:\n"
10393 " --ldes-tranfilter <f> warmup filter: mser5 (default), fixed, none\n"
10394 " --ldes-warmupfrac <x> fraction the fixed filter discards (0.2)\n"
10395 " --ldes-cimethod <m> CI estimator: obm (default), bm, spectral,\n"
10397 " --ldes-cnvgon stop on relative precision instead of on\n"
10398 " the --samples budget\n"
10399 " --ldes-cnvgtol <x> that precision target (0.05); implies\n"
10401 " --slotted run the analytical solver on a discrete\n"
10402 " (slotted) time scale; SolverNC routes to the\n"
10403 " discrete-time product form and refuses a model\n"
10405 " --slotlength <x> the slot in model time units; implies --slotted\n"
10406 " --ldes-slotted run on a discrete (slotted) time scale; a\n"
10407 " sample off the lattice is an error, never\n"
10409 " --ldes-slotlength <x> the slot (1.0); implies --ldes-slotted\n"
10410 " --ldes-replications <n> independent runs, averaged. A single path\n"
10411 " is NOT E[N](t): -a tran over an ensemble\n"
10412 " mean needs this\n"
10413 " --ldes-numthreads <n> workers for those replications\n"
10414 " --ldes-maxtime <s> wall-clock budget; the engine stops early\n"
10415 " and reports stopping=max_time\n"
10416 " --ldes-initsol <v,..> warm-start placement, station-major\n"
10417 " [st0_cl0, st0_cl1, ...]. Add --ldes-tranfilter\n"
10418 " fixed --ldes-warmupfrac 0 to reproduce\n"
10419 " initFromSolver, which assumes the placement\n"
10420 " is already a steady state\n"
10421 " --ldes-rest-url <u> solve on an LDES REST server instead of a\n"
10422 " local binary; same wire format, same numbers\n"
10423 " The model.json is forwarded to the engine BYTE FOR BYTE, so a model\n"
10424 " this port cannot itself parse (a cache with retrieval, an SPN, a\n"
10425 " polling server) is simulated exactly as the MATLAB and Python clients\n"
10426 " simulate it. -a avg, tran, cdf (the empirical response-time law),\n"
10427 " sample, reward and prob (the histogram's residence time at the declared\n"
10428 " per-class counts; --node with --state overrides one station) are\n"
10429 " served. --arith is double only.\n"
10431 "Uncertainty quantification (-s uq) additionally takes:\n"
10432 " --uq-solver <s> the engine run at each design point: mva, nc,\n"
10433 " mam, ba, ctmc, fluid or ssa. REQUIRED: UQ\n"
10434 " computes nothing itself, and defaulting it\n"
10435 " would attribute the numbers to an engine the\n"
10436 " caller never chose\n"
10437 " A model whose service or arrival process is a Prior is a FAMILY of\n"
10438 " models. UQ discretizes each Prior, solves the tensor product of the\n"
10439 " alternatives, and reports the prior-weighted expectation. Under -s uq\n"
10440 " three flags describe the DESIGN and not the engine: --method is\n"
10441 " quadrature (default, and the alias of discrete) or montecarlo,\n"
10442 " --samples the nodes per continuous Prior (11), --seed the Monte Carlo\n"
10443 " stream. The stage solver therefore keeps its own sample count and its\n"
10444 " own seed; --tol, --iter_tol, --iter_max and --cutoff pass through to\n"
10445 " it, since UQ has no convergence of its own. -a posterior prints every\n"
10446 " design point, its weight and the means it substituted, which is what\n"
10447 " says whether the expectation averaged two nearby models or two very\n"
10448 " different ones. Every other solver REFUSES a model carrying a Prior\n"
10449 " rather than lowering it to its mixture moments.\n"
10450 " -a interval answers the OTHER epistemic question, in which a\n"
10451 " parameter is bounded but not distributed: it drops the weights and\n"
10452 " keeps the endpoints. On a single-class closed model of LI\n"
10453 " single-server queues and delays it is the EXACT hull of MVA over the\n"
10454 " demand box (2*(m+2) MVA calls, no design solved at all); otherwise it\n"
10455 " falls back to the range over the solved design points, which for a\n"
10456 " continuous Prior lies strictly inside the true range. The table says\n"
10457 " which, and the fallback warns on stderr: a range that is not an\n"
10458 " enclosure must not read like one.\n"
10460 "Random environments (-s env) read an Environment model.json, whose\n"
10461 " stages each hold a Network and whose transitions carry the stage\n"
10462 " holding times. The stages are solved TRANSIENTLY and coupled: each\n"
10463 " stage starts from the queue lengths the previous one left, and the\n"
10464 " reported means are the per-stage sojourn averages blended by the\n"
10465 " environment probabilities. --method selects the coupling: meanfield\n"
10466 " (default, the reference's, carries the marginal means across a\n"
10467 " switch) or statevec|blend (carries the whole joint distribution).\n"
10468 " meanfield solves each stage with the fluid transient and is double\n"
10469 " only; statevec uniformizes a CTMC and takes the whole --arith ladder.\n"
10470 " --method avg|dec asks instead for a closed-form limit, which carries\n"
10471 " nothing across a switch and iterates nothing: avg solves ONE model\n"
10472 " whose modulated rates are their probEnv-weighted averages (exact as\n"
10473 " the environment gets fast), dec solves each stage in steady state and\n"
10474 " blends by probEnv (exact as it gets slow). A model with an\n"
10475 " environment-declared node breakdown is read from the nodeFailures\n"
10476 " block, in the expanded or the one-stage macro form.\n"
10477 " --tspan bounds the stage horizon and --tran-points its grid; --iter_tol\n"
10478 " and --iter_max drive the fixed point. RespT and ArvR print as nan\n"
10479 " because ENV computes neither -- the reference returns them as NaN too,\n"
10480 " and ResidT carries QLen/Tput.\n"
10482 "Additions specific to this port:\n"
10483 " --arith <mode> double (default) | exact | real:<digits>\n"
10484 " --list-api list the API functions ported so far\n"
10485 " --generate <spec> draw a random model and print it, solving\n"
10486 " nothing. <spec> is a JSON object inline, a\n"
10487 " path to one, or `-` for standard input.\n"
10488 " {\"kind\":\"network\"} draws a flat model and\n"
10489 " prints the line-model JSON document; its keys\n"
10490 " are seed, name, queues, delays, openClasses,\n"
10491 " closedClasses, schedStrat, routingStrat,\n"
10492 " distribution, cclassJobLoad,\n"
10493 " varyingServiceRates, multiServerQueues,\n"
10494 " randomCSNodes, multiChainCS and topology\n"
10495 " (rand|cyclic). A `delays` of -1, the default,\n"
10496 " lets the generator split the stations itself.\n"
10497 " {\"kind\":\"layered\"} draws an LQN and prints\n"
10498 " the .lqnx document; its keys are seed, name,\n"
10499 " clients, levels, tasks, processors,\n"
10500 " populationRange, thinkTimeRange,\n"
10501 " taskMultiRange, procMultiRange,\n"
10502 " hostDemandRange, synchCallRange (each a\n"
10503 " [lo,hi] pair), taskInfProbability and\n"
10504 " procInfProbability. Both draw from\n"
10505 " java.util.Random at `seed`, so a seeded spec\n"
10506 " reproduces; --arith is not read, since a draw\n"
10507 " is a double whatever the model is solved at.\n"
10508 " --api <name> invoke one API function directly\n"
10509 " --args <path> JSON arguments for --api; stdin when omitted\n"
10511 "Solvers and what they honour. Every model-solving solver reads -a avg,\n"
10512 "-f/-i and --method; nothing else is wired, so tolerances, iteration\n"
10513 "caps, seeds and sample counts keep their SolverOptions defaults rather\n"
10514 "than being invented here. mva and nc additionally read -a prob -- mva\n"
10515 "fits a binomial to its own means, nc returns the exact product-form\n"
10516 "probability, so the two disagree by construction. mva also reads -a\n"
10517 "marg, @SolverMVA's getProbMarg: P(n jobs of class r at station i) for\n"
10518 "every (station, class) pair, narrowed by --node / --class and evaluated\n"
10519 "at --marg-states. A closed class takes the Schmidt binomial fitted to\n"
10520 "Q(i,r); an open one takes the station's exact BCMP marginal (Poisson at\n"
10521 "an infinite server, multinomial-geometric at a queue). Both solvers read\n"
10522 "-a normconst, getProbNormConstAggr: nc reports the constant its solve\n"
10523 "already formed, mva RE-ENTERS its analyzer at method='exact', since only\n"
10524 "the exact recursion carries a G -- a model whose branch forms none, an\n"
10525 "open or mixed one above all, reports nan, as the reference does. nc also\n"
10527 "@SolverNC's getCdfRespT: the whole response-time law per (station,\n"
10528 "class) on one logarithmic grid, FCFS stations only, with\n"
10529 "--cdf-algorithm exact (pfqn_stdf, the default) or rd (pfqn_stdf_heur).\n"
10530 "nc reads -a sens as well, @NetworkSolver's getSensitivityTable: the\n"
10531 "derivative of each row's means with respect to its own service rate,\n"
10532 "selected with --sens-method / --sens-scheme / --sens-step. NC is one of\n"
10533 "the two engines whose exact branch differentiates the product-form\n"
10534 "recursion analytically, so auto takes it wherever the model is in its\n"
10535 "scope (single-server queues plus delays, not mixed) and falls back to\n"
10536 "finite differences elsewhere. NOT the -s ctmc -a sens analysis, which\n"
10537 "is getSensitivityRanking, a ranking of rate perturbations and not a\n"
10538 "table of derivatives.\n"
10539 "-a node is @NetworkSolver's getAvgNodeTable and is served by mva, nc,\n"
10540 "mam, ba and ctmc, the model solvers whose runner returns the station\n"
10541 "AvgResult it is built from. It is a DIFFERENT INDEX SPACE from -a avg,\n"
10542 "not a relabelling: the AvgTable has one row per STATION, so a\n"
10543 "ClassSwitch, Router, Fork, Join or Sink never appears in it, yet jobs\n"
10544 "flow through all of them. QLen, Util, RespT and ResidT are the station\n"
10545 "numbers scattered to their node indices and zero elsewhere -- a node\n"
10546 "that is not a station holds no jobs -- while ArvR and Tput are\n"
10547 "recomputed for every node by sn_get_node_arvr_from_tput and\n"
10548 "sn_get_node_tput_from_tput. The reference's finite-capacity-region\n"
10549 "pseudo-node rows are NOT emitted: this port's AvgResult carries no\n"
10550 "per-region queue length or utilization to fill them with.\n"
10552 "auto picks the engine\n"
10553 "with chooseSolverHeur and prints the name it picked; a branch selecting\n"
10554 "JMT or LDES refuses by name rather than substituting another. ctmc reads\n"
10555 "--cutoff and ports -a avg, prob, gen, states, tranprob, sample, reward,\n"
10556 "cdf, first-passt and sens, which are @SolverCTMC's\n"
10557 "getProbSys/getProbSysAggr and the per-station getProb/getProbAggr,\n"
10558 "getInfGen, getStateSpace, getTranProbSysAggr, sampleSys, getAvgReward,\n"
10559 "getCdfRespT, getCdfFirstPassT (state sets via --passage-from and\n"
10560 "--passage-into, as 1-based space rows '3,5' or state rows '0,2;1,1'),\n"
10561 "first-passt-moments (-a firstpasstmom, the same two sets plus\n"
10562 "--passage-orders: exact moments by one linear solve per order, so a\n"
10563 "variance or a skewness costs no truncated curve)\n"
10564 "and getSensitivityRanking. Its --method also takes mdd, which holds the\n"
10565 "reachable set in a decision diagram and solves K coupled level-CTMCs\n"
10566 "instead of the |S|-state generator; it serves -a avg only, having no\n"
10567 "explicit chain to answer the rest from. nc's --method also takes cftp /\n"
10568 "cftp.approx, which draw iid states from the exact stationary law of a\n"
10569 "closed single-class product-form network by coupling from the past;\n"
10570 "they serve -a avg only and their rows carry Monte Carlo error.\n"
10571 "mam ports -a avg,\n"
10572 "prob, cdf, tran and internals, which are @SolverMAM's getProb and\n"
10573 "getProbMarg (the joint (level, phase) law of the queue and its\n"
10574 "per-class marginals, truncated at --cutoff when the model is open),\n"
10575 "getCdfRespT with getPerctRespT beside it, getTranAvg over --tspan (the\n"
10576 "reference forces method ldqbd there, so --method does not select it),\n"
10577 "and getMAMResult, the M/G/1-type internals of a single queue. fluid\n"
10578 "additionally reads -a tran, -a prob, -a cdf, -a var and -a aoi, which\n"
10579 "are @SolverFLD's getTranAvg (the metrics along the trajectory, over\n"
10580 "--tspan or, without one, the horizon the reference's own adaptive loop\n"
10581 "converges at), getProbAggr (a law FITTED to the fluid means, so it does\n"
10582 "not agree with the mva or nc answer by construction), getCdfRespT (the\n"
10583 "response-time law per station and class, read off a marked-fluid\n"
10584 "integration started from the steady state), getMoments/getTranAvgVar\n"
10585 "and getAvgAoI with getCdfAoI beside it -- the last needing method mfq\n"
10586 "and the Source/Queue/Sink topology the age laws are defined for. It\n"
10587 "further reads -a odes, which is\n"
10588 "@SolverFLD/exportODEs, and --notation for the form it writes, and -a\n"
10589 "jacobian, which is @SolverFLD/getJacobian: d f_i / d x_j of the drift,\n"
10590 "differentiated locally over the structure of the system, with the\n"
10591 "equilibria beside it under --equilibria, which needs the\n"
10592 "line-sage-rest backend --symbolic names. Only the smooth methods have\n"
10593 "a Jacobian: min(n,S) has none where the regime switches, so the\n"
10594 "min-scaled drifts are refused by the factor that carries the kink.\n"
10595 "nc, ba and ctmc run\n"
10596 "under every --arith backend, nc's cftp method excepted: its sampler\n"
10597 "works in the log domain, so it refuses 'exact' by name rather than\n"
10598 "answering in a field it does not live in. mdd does run under 'exact'\n"
10599 "(its level solve drops Householder for a rational least squares there),\n"
10600 "but the LEVEL AGGREGATION is still an approximation away from product\n"
10601 "form: exact arithmetic pins the fixed point, not the model. mam is\n"
10602 "double only (its phase-type fitter\n"
10603 "needs transcendental arithmetic) and so is ssa (its sample path is\n"
10604 "generated from exponential clocks) and fluid (LSODA). ba reports a\n"
10605 "BOUND, not an estimate, and ssa a simulation carrying Monte Carlo\n"
10606 "error; neither is comparable with an exact solver except as such.\n"
10608 "Arithmetic: 'exact' computes in arbitrary-precision rationals and\n"
10609 "reports numerator and denominator alongside the double value; 'real'\n"
10610 "computes in fixed high-precision binary floating point, at 50, 100 or\n"
10611 "200 digits (a request in between is rounded up to the next tier).\n"
10613 "--api arguments are a JSON object keyed by the MATLAB parameter names,\n"
10614 "e.g. {\"L\": [[0.6,0.4]], \"N\": [2,1], \"Z\": [1,0.5]}: a 2-D array is a\n"
10615 "matrix (row-major), a 1-D array a row vector, a bare number a scalar.\n"
10616 "A JSON number is read as its shortest round-tripping decimal, so 0.6 is\n"
10617 "3/5 in exact arithmetic; pass a string such as \"1/3\" for anything else.\n",
10634bool install_check() {
10635 bool has_warnings =
false;
10640 const auto warn = [&](
const std::string& text) {
10641 std::fflush(stdout);
10642 std::fprintf(stderr,
"%s\n", text.c_str());
10643 std::fflush(stderr);
10644 has_warnings =
true;
10647 std::printf(
"Checking LINE (C++)...\n");
10648 std::printf(
" line-cli %s\n", kVersion);
10650 std::printf(
"Checking Java runtime (JMT wrapper)...\n");
10651 const std::string java = line::jmt::detail::find_java();
10652 if (java.empty()) {
10653 warn(
"WARNING: no Java runtime was found in LINE_JAVA, JAVA_HOME or on PATH, so the "
10654 "JMT wrapper (-s jmt) cannot run. Install a JRE 8 or later.");
10656 std::printf(
" %s\n", java.c_str());
10659 std::printf(
"Checking JMT...\n");
10660 const std::string jmt_dir = line::jmt::detail::jmt_jar_path();
10661 if (jmt_dir.empty()) {
10667 warn(
"WARNING: JMT.jar was not found, this is required by the JMT wrapper. Download it "
10668 "from https://line-solver.sourceforge.net/latest/JMT.jar into common/, or point "
10669 "LINE_JMT_DIR at the folder holding it, or set LINE_JMT_DOWNLOAD=1 to let the first "
10670 "JMT solve fetch and verify it.");
10672 std::printf(
" %s/JMT.jar\n", jmt_dir.c_str());
10675 std::printf(
"Checking LQNS...\n");
10678 if (banner.empty())
10679 warn(
"WARNING: lqns is not installed, this is required by the LQNS wrapper (-s lqns) "
10680 "for layered models. Download it at: https://github.com/layeredqueuing/dist");
10682 warn(
"WARNING: the installed lqns is too old for LINE, which needs release 6 or "
10683 "later; it reports '" + banner +
"'. Upgrade it from: "
10684 "https://github.com/layeredqueuing/dist");
10689 std::printf(
"Checking qnsolver...\n");
10691 warn(
"WARNING: qnsolver is not installed, this is required by the qns methods of the "
10692 "LQNS wrapper (-s lqns on a Network). "
10693 "It ships with LQNS: https://github.com/layeredqueuing/dist");
10695 std::printf(
"Checking symbolic backend (line-sage-rest)...\n");
10697 warn(std::string(
"WARNING: Docker is not available, so the SageMath symbolic backend "
10698 "cannot start. It is the only computer algebra system this edition "
10699 "reaches and is required by the symbolic methods of "
10700 "SolverCTMC/SolverFluid. Install Docker, then run: "
10703 warn(std::string(
"WARNING: the line-sage-rest image is not present locally, this may be "
10704 "required by some LINE methods. Pull it with: "
10708 std::printf(
"Completed. LINE has warnings.\n");
10710 std::printf(
"Success. LINE is ready to use.\n");
10711 return !has_warnings;
10716 std::printf(
"%-24s %-8s %-24s %s\n",
"function",
"domain",
"arithmetic",
"ported from");
10717 for (
const auto& e :
reg) {
10719 for (std::size_t k = 0; k < e.arith.size(); ++k) {
10720 if (k) modes +=
",";
10723 std::printf(
"%-24s %-8s %-24s %s\n", e.name.c_str(), e.domain.c_str(), modes.c_str(),
10724 e.reference.c_str());
10726 std::printf(
"\n%zu of ~480 API functions ported.\n",
reg.size());
10736 std::string source;
10737 if (path.empty()) {
10738 source =
"standard input";
10739 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
10741 source =
"'" + path +
"'";
10742 std::ifstream in(path.c_str());
10744 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
10747 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
10748 throw line::InputError(
"no --api arguments given: pass --args <path> or a JSON object on "
10749 "standard input (read from " +
10752 return line::reg::Json::parse(text);
10753 }
catch (
const line::reg::Json::parse_error& e) {
10754 throw line::InputError(
"malformed --api arguments in " + source +
": " + e.what());
10769 std::string source;
10770 const std::string::size_type first = arg.find_first_not_of(
" \t\r\n");
10771 if (first != std::string::npos && arg[first] ==
'{') {
10772 source =
"the --generate argument";
10774 }
else if (arg.empty() || arg ==
"-") {
10775 source =
"standard input";
10776 text.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
10778 source =
"'" + arg +
"'";
10779 std::ifstream in(arg.c_str());
10780 if (!in)
throw line::InputError(
"cannot open the --generate spec file " + source);
10781 text.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
10783 if (text.find_first_not_of(
" \t\r\n") == std::string::npos)
10784 throw line::InputError(
"no --generate specification given: pass a JSON object inline, a "
10785 "path to one, or `-` for standard input (read from " +
10788 return line::reg::Json::parse(text);
10789 }
catch (
const line::reg::Json::parse_error& e) {
10790 throw line::InputError(
"malformed --generate specification in " + source +
": " + e.what());
10796 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10798 if (!v.is_number())
10799 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a number");
10800 const double d = v.get<
double>();
10801 if (d != std::floor(d))
10802 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a whole number");
10803 return static_cast<long>(d);
10807double gen_real(
const line::reg::Json& j,
const char* key,
double dflt) {
10808 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10810 if (!v.is_number())
10811 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a number");
10812 return v.get<
double>();
10817 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10819 if (!v.is_boolean())
10820 throw line::InputError(std::string(
"--generate: '") + key +
"' must be true or false");
10821 return v.get<
bool>();
10825std::string gen_str(
const line::reg::Json& j,
const char* key,
const std::string& dflt) {
10826 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10828 if (!v.is_string())
10829 throw line::InputError(std::string(
"--generate: '") + key +
"' must be a string");
10830 return v.get<std::string>();
10835 if (!j.contains(key) || j.at(key).is_null())
return dflt;
10837 if (!v.is_array() || v.size() != 2 || !v[0].is_number() || !v[1].is_number())
10839 "' must be a two-element array of numbers");
10849void gen_check_keys(
const line::reg::Json& j,
const char*
const* allowed, std::size_t n) {
10850 for (line::reg::Json::const_iterator it = j.begin(); it != j.end(); ++it) {
10852 for (std::size_t k = 0; k < n && !ok; ++k) ok = it.key() == allowed[k];
10855 for (std::size_t k = 0; k < n; ++k) {
10856 if (k) names +=
", ";
10857 names += allowed[k];
10860 "'; this kind reads: " + names);
10882std::string normalize_analysis(
const std::string& a) {
10884 if (a ==
"cdf-respt" || a ==
"cdfrespt")
return "cdf";
10885 if (a ==
"cdf-passt" || a ==
"cdfpasst")
return "cdfpasst";
10886 if (a ==
"first-passt" || a ==
"cdf-firstpasst" || a ==
"cdffirstpasst")
return "firstpasst";
10887 if (a ==
"first-passt-moments" || a ==
"firstpasst-moments" || a ==
"firstpasstmoments")
10888 return "firstpasstmom";
10889 if (a ==
"perct-respt" || a ==
"perctrespt")
return "perct";
10890 if (a ==
"tran-avg" || a ==
"tranavg")
return "tran";
10891 if (a ==
"tran-cdf-respt" || a ==
"trancdfrespt")
return "trancdf";
10892 if (a ==
"tran-cdf-passt" || a ==
"trancdfpasst")
return "trancdfpasst";
10893 if (a ==
"tran-prob" || a ==
"tranprob-sys-aggr")
return "tranprob";
10894 if (a ==
"generator")
return "gen";
10895 if (a ==
"state-space" || a ==
"statespace")
return "states";
10896 if (a ==
"reward-steady" || a ==
"rewardsteady")
return "reward";
10897 if (a ==
"reward-value" || a ==
"rewardvalue")
return "rewardvalue";
10898 if (a ==
"node-chain" || a ==
"node-chain-table")
return "nodechain";
10903 if (a ==
"prob-aggr" || a ==
"prob-sys" || a ==
"prob-sys-aggr")
return "prob";
10904 if (a ==
"prob-marg" || a ==
"probmarg")
return "marg";
10905 if (a ==
"prob-sys-marg" || a ==
"probsysmarg" || a ==
"sys-marg")
return "sysmarg";
10906 if (a ==
"sample-aggr" || a ==
"sample-sys" || a ==
"sample-sys-aggr")
return "sample";
10912 if (a ==
"stage")
return "avg";
10917std::vector<std::string> analysis_list(
const std::string& spec) {
10918 std::vector<std::string> out;
10919 std::size_t at = 0;
10920 while (at <= spec.size()) {
10921 const std::size_t comma = spec.find(
',', at);
10923 spec.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
10925 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.front())))
10926 tok.erase(tok.begin());
10927 while (!tok.empty() && std::isspace(
static_cast<unsigned char>(tok.back())))
10930 throw line::InputError(
"-a takes a comma-separated list of analyses and one entry of '" +
10931 spec +
"' is empty");
10935 if (normalize_analysis(tok) ==
"all") {
10936 out.push_back(
"avg");
10937 out.push_back(
"sys");
10939 out.push_back(normalize_analysis(tok));
10941 if (comma == std::string::npos)
break;
10944 if (out.empty())
throw line::InputError(
"-a takes at least one analysis");
10949 std::string file, input =
"json", output =
"readable", solver =
"auto", analysis =
"avg";
10950 std::string arith =
"double", api, args;
10951 bool help =
false, help_all =
false, version =
false, list =
false;
10961 bool find_solver =
false, find_solver_all =
false;
10962 std::string find_solver_metric;
10964 bool install =
false;
10975 std::string generate;
10976 bool generate_given =
false;
10985 bool input_given =
false;
11000bool has_jsim_extension(
const std::string& file) {
11001 const std::string::size_type
dot = file.find_last_of(
'.');
11002 if (dot == std::string::npos)
return false;
11003 std::string ext = file.substr(dot + 1);
11004 for (std::size_t i = 0; i < ext.size(); ++i)
11005 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11006 return ext ==
"jsim" || ext ==
"jsimg" || ext ==
"jsimw";
11010bool has_pnml_extension(
const std::string& file) {
11011 const std::string::size_type
dot = file.find_last_of(
'.');
11012 if (dot == std::string::npos)
return false;
11013 std::string ext = file.substr(dot + 1);
11014 for (std::size_t i = 0; i < ext.size(); ++i)
11015 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11016 return ext ==
"pnml";
11020bool has_lqn_extension(
const std::string& file) {
11021 const std::string::size_type
dot = file.find_last_of(
'.');
11022 if (dot == std::string::npos)
return false;
11023 std::string ext = file.substr(dot + 1);
11024 for (std::size_t i = 0; i < ext.size(); ++i)
11025 ext[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ext[i])));
11026 return ext ==
"lqnx" || ext ==
"xml";
11029Options parse_args(
int argc,
char** argv) {
11031 for (
int i = 1; i < argc; ++i) {
11032 std::string a = argv[i];
11033 auto next = [&](
const char* what) -> std::string {
11034 if (i + 1 >= argc)
throw line::InputError(std::string(
"missing value after ") + what);
11037 if (a ==
"-h" || a ==
"--help") o.help =
true;
11038 else if (a ==
"--help-all" || a ==
"--help-full") o.help_all =
true;
11039 else if (a ==
"-V" || a ==
"--version") o.version =
true;
11040 else if (a ==
"--install") o.install =
true;
11041 else if (a ==
"--list-api") o.list =
true;
11042 else if (a ==
"--generate") {
11045 o.generate_given =
true;
11048 if (i + 1 < argc && (argv[i + 1][0] !=
'-' || argv[i + 1][1] ==
'\0'))
11049 o.generate = argv[++i];
11051 else if (a ==
"--find-solver" || a ==
"--find-method" || a ==
"--help-model") {
11052 o.find_solver =
true;
11055 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
11056 }
else if (a ==
"--find-solver-all" || a ==
"--find-method-all") {
11057 o.find_solver =
true;
11058 o.find_solver_all =
true;
11059 if (i + 1 < argc && argv[i + 1][0] !=
'-') o.find_solver_metric = argv[++i];
11061 else if (a ==
"-f" || a ==
"--file") o.file = next(
"-f");
11062 else if (a ==
"-i" || a ==
"--input") { o.input = next(
"-i"); o.input_given =
true; }
11063 else if (a ==
"-o" || a ==
"--output") o.output = next(
"-o");
11064 else if (a ==
"-s" || a ==
"--solver") o.solver = next(
"-s");
11065 else if (a ==
"-a" || a ==
"--analysis") o.analysis = next(
"-a");
11066 else if (a ==
"--arith") o.arith = next(
"--arith");
11067 else if (a ==
"-p" || a ==
"--port") {
11068 const std::string v = next(
"-p");
11069 const long n = std::atol(v.c_str());
11070 if (n < 1 || n > 65535)
11071 throw line::InputError(
"-p takes a TCP port in 1..65535 (got '" + v +
"')");
11072 o.port =
static_cast<int>(n);
11073 }
else if (a ==
"-m" || a ==
"--maxreq") {
11074 const std::string v = next(
"-m");
11075 const long n = std::atol(v.c_str());
11078 "-m is the number of requests the server serves before quitting and must be "
11079 "positive; omit it to serve indefinitely (got '" + v +
"')");
11080 o.maxreq =
static_cast<int>(n);
11082 else if (a ==
"--api") o.api = next(
"--api");
11083 else if (a ==
"--args") o.args = next(
"--args");
11084 else if (a ==
"--method") o.knobs.method = next(
"--method");
11085 else if (a ==
"--qrf-params") o.knobs.qrf_params = next(
"--qrf-params");
11086 else if (a ==
"--qrf-alpha") o.knobs.qrf_alpha = next(
"--qrf-alpha");
11087 else if (a ==
"--level") {
11088 const std::string v = next(
"--level");
11089 const int lv = std::atoi(v.c_str());
11090 if (lv < 1)
throw line::InputError(
"--level must be a positive integer (got '" + v +
"')");
11091 o.knobs.level = lv;
11093 else if (a ==
"--samples") {
11094 const std::string v = next(
"--samples");
11095 const double d = std::atof(v.c_str());
11097 throw line::InputError(
"--samples must be a positive count (got '" + v +
"')");
11098 o.knobs.samples =
static_cast<std::size_t
>(d);
11099 }
else if (a ==
"-d" || a ==
"--seed") {
11100 const std::string v = next(
"--seed");
11101 o.knobs.seed = std::strtoul(v.c_str(),
nullptr, 10);
11102 if (o.knobs.seed == 0)
11103 throw line::InputError(
"--seed must be a positive integer (got '" + v +
"')");
11104 }
else if (a ==
"--warmupfrac") {
11105 const std::string v = next(
"--warmupfrac");
11106 const double f = std::atof(v.c_str());
11107 if (!(f >= 0.0 && f < 1.0))
11109 "--warmupfrac is the fraction of the path discarded before the means are "
11110 "taken and must lie in [0,1) (got '" + v +
"')");
11111 o.knobs.warmupfrac = f;
11112 }
else if (a ==
"--pstar") {
11113 const std::string v = next(
"--pstar");
11114 const double ps = std::atof(v.c_str());
11117 "--pstar is the exponent of the fluid p-norm smoothing and must be positive "
11118 "(got '" + v +
"')");
11119 o.knobs.pstar = ps;
11120 }
else if (a ==
"--busyperiod" || a ==
"--busyperiod-subnet") {
11123 const bool orders = (a ==
"--busyperiod");
11124 const std::string v = next(orders ?
"--busyperiod" :
"--busyperiod-subnet");
11125 std::vector<std::size_t>& into = orders ? o.knobs.busy_orders : o.knobs.busy_subnet;
11126 std::size_t at = 0;
11127 while (at <= v.size()) {
11128 const std::size_t comma = v.find(
',', at);
11129 const std::string tok =
11130 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11131 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos ||
11132 std::atol(tok.c_str()) < 1)
11134 std::string(orders ?
"--busyperiod takes a comma-separated list of "
11136 :
"--busyperiod-subnet takes a comma-separated list of "
11137 "1-based station indexes") +
11138 " (got '" + v +
"')");
11139 into.push_back(
static_cast<std::size_t
>(std::atol(tok.c_str())));
11140 if (comma == std::string::npos)
break;
11143 }
else if (a ==
"--tol") o.knobs.tol = std::atof(next(
"--tol").c_str());
11144 else if (a ==
"--iter_tol") o.knobs.iter_tol = std::atof(next(
"--iter_tol").c_str());
11145 else if (a ==
"--iter_max") o.knobs.iter_max = std::atoi(next(
"--iter_max").c_str());
11146 else if (a ==
"--max-states") {
11147 const std::string v = next(
"--max-states");
11148 const long long n = std::atoll(v.c_str());
11151 "--max-states truncates an open agent's queue-length dimension and takes a "
11152 "positive state count (got '" + v +
"')");
11153 o.knobs.max_states = n;
11155 else if (a ==
"--multiserver") o.knobs.multiserver = next(
"--multiserver");
11156 else if (a ==
"--fork-join" || a ==
"--fork_join")
11157 o.knobs.fork_join = next(
"--fork-join");
11158 else if (a ==
"--tran-points" || a ==
"--tran_points") {
11159 const std::string v = next(
"--tran-points");
11160 const long n = std::atol(v.c_str());
11163 "--tran-points is the number of points on the transient grid and needs at "
11164 "least two, a start and an end (got '" + v +
"')");
11165 o.knobs.tran_points =
static_cast<std::size_t
>(n);
11167 else if (a ==
"--mdd-tol" || a ==
"--mdd_tol") {
11168 const std::string v = next(
"--mdd-tol");
11169 const double d = std::atof(v.c_str());
11171 throw line::InputError(
"--mdd-tol must be a positive tolerance (got '" + v +
"')");
11172 o.knobs.mdd_tol = d;
11173 }
else if (a ==
"--mdd-maxiter" || a ==
"--mdd_maxiter") {
11174 const std::string v = next(
"--mdd-maxiter");
11175 const long n = std::atol(v.c_str());
11178 "--mdd-maxiter must be a positive sweep count (got '" + v +
"')");
11179 o.knobs.mdd_maxiter =
static_cast<int>(n);
11181 else if (a ==
"--fj-accuracy") {
11182 const std::string v = next(
"--fj-accuracy");
11183 const long n = std::atol(v.c_str());
11186 "--fj-accuracy is the FJ_codes truncation C of the queue-length difference "
11187 "between the two fork-join branches and must be at least 1 (got '" + v +
"')");
11188 o.knobs.fj_accuracy =
static_cast<int>(n);
11189 }
else if (a ==
"--fj-tmode") {
11190 const std::string v = next(
"--fj-tmode");
11191 if (v !=
"NARE" && v !=
"Sylves")
11193 "--fj-tmode selects how computeT.m solves for the T matrix and is 'NARE' (the "
11194 "Riccati route, the default) or 'Sylves' (the fixed-point iteration); got '" +
11196 o.knobs.fj_tmode = v;
11197 }
else if (a ==
"--timescale") {
11198 const std::string v = next(
"--timescale");
11199 if (v !=
"auto" && v !=
"discrete" && v !=
"continuous")
11201 "--timescale decides whether the model is read on a slot lattice and is "
11202 "'auto' (the default), 'discrete' or 'continuous'; got '" + v +
"'");
11203 o.knobs.timescale = v;
11205 else if (a ==
"--force") {
11206 o.knobs.force =
true;
11208 else if (a ==
"--cutoff") {
11209 const std::string v = next(
"--cutoff");
11210 if (v.find(
',') != std::string::npos || v.find(
';') != std::string::npos) {
11211 o.knobs.cutoff_mat = parse_cutoff_matrix(v);
11212 if (o.knobs.cutoff_mat.empty())
11214 "--cutoff takes a number or a per-(station,class) matrix written "
11215 "'r1c1,r1c2;r2c1,r2c2' (got '" + v +
"')");
11217 const double d = std::atof(v.c_str());
11220 "--cutoff must be a positive job count per open class (got '" + v +
"')");
11221 o.knobs.cutoff = d;
11223 }
else if (a ==
"--tspan" || a ==
"--timespan") {
11224 const std::string v = next(
"--tspan");
11230 std::string::size_type sep = v.find(
':');
11231 if (sep == std::string::npos) sep = v.find(
',');
11234 const double lo = sep == std::string::npos ? 0.0 : std::atof(v.substr(0, sep).c_str());
11235 const double hi = std::atof(
11236 (sep == std::string::npos ? v : v.substr(sep + 1)).c_str());
11239 if (!(hi > lo) || !(lo >= 0.0) || !std::isfinite(hi))
11241 "--tspan must be a finite horizon 0 <= t0 < t1, given as <t1>, <t0>:<t1> or "
11242 "<t0>,<t1> (got '" + v +
"')");
11245 }
else if (a ==
"-n" || a ==
"--node") {
11246 const std::string v = next(
"--node");
11247 const long n = std::atol(v.c_str());
11249 throw line::InputError(
"--node must be a positive 1-based node index (got '" + v +
11251 o.knobs.node =
static_cast<std::size_t
>(n);
11252 }
else if (a ==
"-c" || a ==
"--class") {
11253 const std::string v = next(
"--class");
11254 const long c = std::atol(v.c_str());
11256 throw line::InputError(
"--class must be a positive 1-based class index (got '" + v +
11258 o.knobs.jobclass =
static_cast<std::size_t
>(c);
11259 }
else if (a ==
"--marg-states" || a ==
"--marg_states") {
11261 const std::string v = next(
"--marg-states");
11262 std::size_t at = 0;
11263 while (at <= v.size()) {
11264 const std::size_t comma = v.find(
',', at);
11265 const std::string tok =
11266 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11267 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
11269 "--marg-states takes a comma-separated list of non-negative job counts "
11270 "(got '" + v +
"')");
11271 o.knobs.marg_states.push_back(std::atol(tok.c_str()));
11272 if (comma == std::string::npos)
break;
11275 }
else if (a ==
"--state") {
11279 const std::string v = next(
"--state");
11280 std::size_t at = 0;
11281 while (at <= v.size()) {
11282 const std::size_t comma = v.find(
',', at);
11283 const std::string tok =
11284 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11285 if (tok.empty() || tok.find_first_not_of(
"0123456789") != std::string::npos)
11287 "--state is the state vector -a prob asks about and takes a "
11288 "comma-separated list of non-negative counts (got '" + v +
"')");
11289 o.knobs.state.push_back(std::atol(tok.c_str()));
11290 if (comma == std::string::npos)
break;
11293 }
else if (a ==
"--events") {
11294 const std::string v = next(
"--events");
11295 const double d = std::atof(v.c_str());
11298 "--events is the length of a sampled trajectory and must be a positive event "
11299 "count (got '" + v +
"')");
11300 o.knobs.events =
static_cast<std::size_t
>(d);
11301 }
else if (a ==
"--timestep") {
11302 const std::string v = next(
"--timestep");
11303 const double d = std::atof(v.c_str());
11304 if (!(d > 0.0) || !std::isfinite(d))
11306 "--timestep is the fixed output step of a transient analysis and must be a "
11307 "positive finite time (got '" + v +
"')");
11308 o.knobs.timestep = d;
11309 }
else if (a ==
"--percentiles") {
11310 const std::string v = next(
"--percentiles");
11311 std::size_t at = 0;
11312 while (at <= v.size()) {
11313 const std::size_t comma = v.find(
',', at);
11314 const std::string tok =
11315 v.substr(at, comma == std::string::npos ? std::string::npos : comma - at);
11318 "--percentiles takes a comma-separated list of levels (got '" + v +
"')");
11319 double p = std::atof(tok.c_str());
11326 if (p > 1.0) p /= 100.0;
11327 if (!(p > 0.0) || !(p < 1.0))
11329 "--percentiles levels lie strictly inside (0,1) as fractions or (0,100) "
11330 "as percents; the 100th percentile of an unbounded law is not finite "
11331 "(got '" + tok +
"')");
11332 o.knobs.percentiles.push_back(p);
11333 if (comma == std::string::npos)
break;
11336 }
else if (a ==
"--reward-name" || a ==
"--reward_name") {
11337 o.knobs.reward_name = next(
"--reward-name");
11338 }
else if (a ==
"--notation") {
11339 const std::string v = next(
"--notation");
11342 o.knobs.notation = v;
11343 }
else if (a ==
"--symbolic") {
11347 o.knobs.symbolic = next(
"--symbolic");
11348 }
else if (a ==
"--equilibria") o.knobs.equilibria =
true;
11349 else if (a ==
"--perm-engine") {
11352 o.knobs.method_perm = next(
"--perm-engine");
11353 }
else if (a ==
"--transient-method") {
11356 o.knobs.transient_method = next(
"--transient-method");
11357 }
else if (a ==
"--rate-sched") {
11358 o.knobs.rate_sched = next(
"--rate-sched");
11359 }
else if (a ==
"--ctmc-tv-ngrid") {
11360 const std::string v = next(
"--ctmc-tv-ngrid");
11361 const long n = std::atol(v.c_str());
11363 throw line::InputError(
"--ctmc-tv-ngrid is the grid size of the rate_sched "
11364 "propagator and must be an integer >= 2 (got '" + v +
"')");
11365 o.knobs.ctmc_tv_ngrid =
static_cast<std::size_t
>(n);
11366 }
else if (a ==
"--fau-epsilon") {
11367 const std::string v = next(
"--fau-epsilon");
11368 const double d = std::atof(v.c_str());
11369 if (!(d > 0.0) || !std::isfinite(d))
11371 "--fau-epsilon is the probability mass the transient grid may discard and "
11372 "must be a positive finite number (got '" + v +
"')");
11373 o.knobs.fau_epsilon = d;
11374 }
else if (a ==
"--fau-delta") {
11375 const std::string v = next(
"--fau-delta");
11376 const double d = std::atof(v.c_str());
11377 if (!(d >= 0.0) || !std::isfinite(d))
11379 "--fau-delta is the occupancy below which a state is dropped and must be a "
11380 "nonnegative finite number (got '" + v +
"')");
11381 o.knobs.fau_delta = d;
11382 }
else if (a ==
"--cdf-algorithm") {
11385 o.knobs.cdf_algorithm = next(
"--cdf-algorithm");
11386 }
else if (a ==
"--passage-from") o.knobs.passage_from = next(
"--passage-from");
11387 else if (a ==
"--passage-into") o.knobs.passage_into = next(
"--passage-into");
11388 else if (a ==
"--passage-method") o.knobs.passage_method = next(
"--passage-method");
11389 else if (a ==
"--passage-orders")
11390 o.knobs.passage_orders =
static_cast<std::size_t
>(std::stoul(next(
"--passage-orders")));
11391 else if (a ==
"--no-interlocking") o.knobs.no_interlocking =
true;
11392 else if (a ==
"--interlock-method") o.knobs.interlock_method = next(
"--interlock-method");
11393 else if (a ==
"--interlock-maxpaths")
11394 o.knobs.interlock_maxpaths = std::stod(next(
"--interlock-maxpaths"));
11395 else if (a ==
"--interlock-refpath-scope")
11396 o.knobs.interlock_refpath_scope = next(
"--interlock-refpath-scope");
11397 else if (a ==
"--layer-solver") o.knobs.layer_solver = next(
"--layer-solver");
11398 else if (a ==
"--stage-solver") o.knobs.stage_solver = next(
"--stage-solver");
11399 else if (a ==
"--map-env") o.knobs.map_env = next(
"--map-env");
11400 else if (a ==
"--map-env-method") o.knobs.map_env_method = next(
"--map-env-method");
11401 else if (a ==
"--map-env-maxstages")
11402 o.knobs.map_env_maxstages =
static_cast<std::size_t
>(std::stoul(next(
"--map-env-maxstages")));
11403 else if (a ==
"--ln-transient") o.knobs.ln_transient = next(
"--ln-transient");
11404 else if (a ==
"--ln-transient-channels")
11405 o.knobs.ln_transient_channels = next(
"--ln-transient-channels");
11406 else if (a ==
"--sens-method") o.knobs.sens_method = next(
"--sens-method");
11407 else if (a ==
"--sens-scheme") o.knobs.sens_scheme = next(
"--sens-scheme");
11408 else if (a ==
"--sens-step") {
11409 const std::string v = next(
"--sens-step");
11410 const double h = std::atof(v.c_str());
11411 if (!(h > 0.0) || !(h < 1.0))
11413 "--sens-step is the RELATIVE rate perturbation and must lie in (0,1) (got '" +
11415 o.knobs.sens_step = h;
11417 else if (a ==
"--uq-solver") o.knobs.uq_solver = next(
"--uq-solver");
11418 else if (a ==
"--keep") o.knobs.keep =
true;
11419 else if (a ==
"--verbose") o.knobs.verbose =
true;
11420 else if (a ==
"--remote") o.knobs.remote =
true;
11421 else if (a ==
"--remote-url") {
11424 o.knobs.remote_url = next(
"--remote-url");
11425 o.knobs.remote =
true;
11427 else if (a ==
"--timeout") {
11428 const std::string v = next(
"--timeout");
11429 const long s = std::atol(v.c_str());
11431 throw line::InputError(
"--timeout is a deadline in seconds and must be positive "
11432 "(got '" + v +
"')");
11433 o.knobs.timeout_seconds =
static_cast<int>(s);
11435 else if (a ==
"--repeat") {
11436 const std::string v = next(
"--repeat");
11437 const long n = std::atol(v.c_str());
11439 throw line::InputError(
"--repeat must be a positive run count (got '" + v +
"')");
11440 o.knobs.repeat =
static_cast<int>(n);
11443 else if (a ==
"--ldes-tranfilter") {
11444 const std::string v = next(
"--ldes-tranfilter");
11445 if (v !=
"mser5" && v !=
"fixed" && v !=
"none")
11447 "--ldes-tranfilter selects the warmup filter and is mser5, fixed or none (got '" +
11449 o.knobs.ldes_tranfilter = v;
11451 else if (a ==
"--ldes-warmupfrac") {
11452 const std::string v = next(
"--ldes-warmupfrac");
11453 const double d = std::atof(v.c_str());
11454 if (!(d >= 0.0 && d < 1.0))
11456 "--ldes-warmupfrac is the fraction of the run the fixed filter discards and "
11457 "lies in [0,1) (got '" + v +
"')");
11458 o.knobs.ldes_warmupfrac = d;
11460 else if (a ==
"--ldes-cimethod") {
11461 const std::string v = next(
"--ldes-cimethod");
11462 if (v !=
"obm" && v !=
"bm" && v !=
"spectral" && v !=
"none")
11464 "--ldes-cimethod selects the confidence-interval estimator and is obm, bm, "
11465 "spectral or none (got '" + v +
"')");
11466 o.knobs.ldes_cimethod = v;
11468 else if (a ==
"--ldes-cnvgon") o.knobs.ldes_cnvgon =
true;
11469 else if (a ==
"--ldes-cnvgtol") {
11472 const std::string v = next(
"--ldes-cnvgtol");
11473 const double d = std::atof(v.c_str());
11474 if (!(d > 0.0 && d < 1.0))
11476 "--ldes-cnvgtol is a RELATIVE precision target and lies in (0,1) (got '" + v +
11478 o.knobs.ldes_cnvgtol = d;
11479 o.knobs.ldes_cnvgon =
true;
11481 else if (a ==
"--ldes-slotted") o.knobs.ldes_slotted =
true;
11482 else if (a ==
"--slotted") o.knobs.slotted =
true;
11483 else if (a ==
"--slotlength") {
11485 const std::string v = next(
"--slotlength");
11486 const double d = std::atof(v.c_str());
11489 "--slotlength is the slot of the discrete time scale and must be positive "
11490 "(got '" + v +
"')");
11491 o.knobs.slotlength = d;
11492 o.knobs.slotted =
true;
11494 else if (a ==
"--ldes-slotlength") {
11496 const std::string v = next(
"--ldes-slotlength");
11497 const double d = std::atof(v.c_str());
11500 "--ldes-slotlength is the slot of the discrete time scale and must be positive "
11501 "(got '" + v +
"')");
11502 o.knobs.ldes_slotlength = d;
11503 o.knobs.ldes_slotted =
true;
11505 else if (a ==
"--jmt-replications") {
11506 const std::string v = next(
"--jmt-replications");
11507 const long n = std::atol(v.c_str());
11510 "--jmt-replications is a positive count of independent runs (got '" + v +
"')");
11511 o.knobs.jmt_replications =
static_cast<int>(n);
11513 else if (a ==
"--ldes-replications") {
11514 const std::string v = next(
"--ldes-replications");
11515 const long n = std::atol(v.c_str());
11518 "--ldes-replications is a positive count of independent runs (got '" + v +
"')");
11519 o.knobs.ldes_replications =
static_cast<int>(n);
11521 else if (a ==
"--ldes-numthreads") {
11522 const std::string v = next(
"--ldes-numthreads");
11523 const long n = std::atol(v.c_str());
11526 "--ldes-numthreads is a positive worker count (got '" + v +
"')");
11527 o.knobs.ldes_numthreads =
static_cast<int>(n);
11529 else if (a ==
"--ldes-maxtime") {
11530 const std::string v = next(
"--ldes-maxtime");
11531 const double d = std::atof(v.c_str());
11534 "--ldes-maxtime is a wall-clock budget in seconds and must be positive (got '" +
11536 o.knobs.ldes_maxtime = d;
11538 else if (a ==
"--ldes-initsol") {
11541 const std::string v = next(
"--ldes-initsol");
11543 while (b <= v.size()) {
11544 const std::size_t e = v.find(
',', b);
11545 const std::string tok =
11546 v.substr(b, e == std::string::npos ? std::string::npos : e - b);
11549 "--ldes-initsol is a comma-separated placement with no empty entry (got '" +
11551 o.knobs.ldes_initsol.push_back(std::atof(tok.c_str()));
11552 if (e == std::string::npos)
break;
11556 else if (a ==
"--ldes-rest-url") o.knobs.ldes_rest_url = next(
"--ldes-rest-url");
11557 else if (a ==
"-v" || a ==
"--verbosity") {
11558 const std::string v = next(a.c_str());
11563 if (v !=
"silent" && v !=
"standard" && v !=
"normal" && v !=
"debug" &&
11566 "-v takes silent, standard (the JAR spells it normal) or debug; got '" + v +
11568 o.knobs.verbosity = (v ==
"normal") ?
"standard" : v;
11570 else if (!a.empty() && a[0] ==
'-')
11606 if (o.file.empty() && !g_stdin_loaded)
11607 throw line::InputError(
"--find-solver reports on a model; name one with -f");
11608 const bool layered = o.file.empty()
11611 if (!o.input_given && layered)
11613 "--find-solver reports on a flat Network model; a layered one is solved by -s ln "
11614 "and -s lqns, which this port reaches through the -i lqnx path");
11615 if (o.output !=
"readable" && o.output !=
"json")
11617 "'; --find-solver reports as: readable, json");
11620 net.
get_struct(), o.find_solver_metric, o.find_solver_all);
11621 if (o.output !=
"json") {
11632 for (std::size_t i = 0; i < rows.size(); ++i) {
11634 c[
"solver"] = rows[i].solver;
11635 c[
"method"] = rows[i].method;
11636 c[
"runnable"] = rows[i].runnable;
11637 c[
"methodClass"] = rows[i].method_class;
11638 c[
"reason"] = rows[i].reason;
11640 for (std::size_t k = 0; k < rows[i].metrics.size(); ++k) ms.push_back(rows[i].metrics[k]);
11642 cands.push_back(c);
11645 body[
"type"] =
"FindSolver";
11646 body[
"candidates"] = cands;
11647 j[
"findSolver"] = body;
11648 emit_document(dump_document(j));
11656 if (!o.api.empty()) {
11657 if (o.output !=
"readable" && o.output !=
"json")
11659 "'; accepted forms are: readable, json");
11662 if (o.output ==
"json")
11663 emit_document(dump_document(result, 2));
11671 if (!o.input_given && has_lqn_extension(o.file)) o.input =
"lqnx";
11673 if (!o.input_given && has_jsim_extension(o.file)) o.input =
"jsimg";
11675 if (!o.input_given && has_pnml_extension(o.file)) o.input =
"pnml";
11688 const bool sniffable = o.input !=
"lqnx" && o.input !=
"xml" && o.input !=
"pnml" &&
11689 o.input.compare(0, 4,
"jsim") != 0;
11690 if (sniffable && !o.file.empty() && !has_pnml_extension(o.file) &&
11693 else if (sniffable && o.file.empty() && g_stdin_loaded &&
11696 if (o.input ==
"lqnx" || o.input ==
"xml") {
11697 if (o.output !=
"readable" && o.output !=
"json" && o.output !=
"layers")
11699 "'; accepted forms on the layered path are: readable, "
11707 const std::vector<std::string> as = analysis_list(o.analysis);
11708 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
11709 const int rc = solve_lqn_dispatch(o.arith, o.solver, as[i], o.output, o.file,
11711 if (rc != 0)
return rc;
11714 return solve_lqn_dispatch(o.arith, o.solver, as.back(), o.output, o.file, o.knobs);
11721 if (o.input ==
"jsim" || o.input ==
"jsimg" || o.input ==
"jsimw")
11722 g_jsim_input =
true;
11723 else if (o.input ==
"pnml")
11724 g_pnml_input =
true;
11725 else if (o.input !=
"json")
11727 "the model-solving path reads -i json for a Network model, -i jsim|jsimg|jsimw "
11728 "for a JMT simulation document, -i pnml for a place/transition net and "
11729 "-i lqnx|xml for a layered one (got '" + o.input +
"')");
11752 if (o.output ==
"jsim" || o.output ==
"jsimg" || o.output ==
"jsimw") {
11761 if (o.knobs.seed) wopt.
seed =
static_cast<long>(o.knobs.seed);
11762 if (o.knobs.samples) wopt.
max_samples =
static_cast<double>(o.knobs.samples);
11767 if (o.output !=
"readable" && o.output !=
"json")
11769 "'; accepted forms are: readable, json, jsimg");
11770 g_json_output = (o.output ==
"json");
11773 const std::vector<std::string> as = analysis_list(o.analysis);
11774 for (std::size_t i = 0; i + 1 < as.size(); ++i) {
11775 const int rc = solve_model_dispatch(o.arith, o.solver, as[i], o.file, o.knobs);
11776 if (rc != 0)
return rc;
11779 return solve_model_dispatch(o.arith, o.solver, as.back(), o.file, o.knobs);
11799 if (fd_ < 0)
return;
11800 const char* tmpdir = std::getenv(
"TMPDIR");
11802 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-cap-XXXXXX";
11803 std::vector<char> buf(path.begin(), path.end());
11804 buf.push_back(
'\0');
11805 tfd_ = ::mkstemp(&buf[0]);
11806 if (tfd_ < 0)
return;
11807 path_.assign(&buf[0]);
11808 std::fflush(fd_ == 1 ? stdout : stderr);
11809 saved_ = ::dup(fd_);
11820 if (!path_.empty()) {
11821 std::remove(path_.c_str());
11829 if (path_.empty())
return std::string();
11830 std::ifstream in(path_.c_str());
11831 std::string out((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
11833 std::remove(path_.c_str());
11842 if (saved_ < 0)
return;
11843 std::fflush(fd_ == 1 ? stdout : stderr);
11844 ::dup2(saved_, fd_);
11876 reset_invocation_state(
false);
11880 return "line-cli: cannot create a capture file for the response\n";
11887 err = std::string(
"line-cli: ") + e.what() +
"\n";
11888 }
catch (
const std::exception& e) {
11890 err = std::string(
"line-cli: unexpected failure: ") + e.what() +
"\n";
11892 std::string out = cap.
take();
11895 return err.empty() ? out : out + err;
11911 std::printf(
"--------------------------------------------------------------------\n");
11912 std::printf(
"LINE Solver - Command Line Interface (C++)\n");
11913 std::printf(
"Copyright (c) 2012-2026, QORE Lab, Imperial College London\n");
11914 std::printf(
"Version %s. All rights reserved.\n", kVersion);
11915 std::printf(
"--------------------------------------------------------------------\n");
11916 std::printf(
"Running in server mode on port %d.\n", base.port);
11918 std::printf(
"Quitting after %d request(s).\n", base.maxreq);
11919 std::fflush(stdout);
11922 while (base.maxreq == 0 || served < base.maxreq) {
11923 const bool ok = server.
serve_one([&](
const std::string& msg) -> std::string {
11924 const std::string::size_type nl = msg.find(
'\n');
11925 if (nl == std::string::npos)
11926 return "line-cli: the request's first line is the argument list and its "
11927 "remainder is the model document; this message has no newline\n";
11928 const std::string argline = msg.substr(0, nl);
11929 const std::string model = msg.substr(nl + 1);
11931 const char* tmpdir = std::getenv(
"TMPDIR");
11933 std::string(tmpdir && *tmpdir ? tmpdir :
"/tmp") +
"/line-cli-req-XXXXXX";
11934 std::vector<char> nb(path.begin(), path.end());
11935 nb.push_back(
'\0');
11936 const int mfd = ::mkstemp(&nb[0]);
11937 if (mfd < 0)
return "line-cli: cannot stage the client model\n";
11939 path.assign(&nb[0]);
11941 std::ofstream mf(path.c_str());
11950 std::vector<std::string> toks;
11951 std::string::size_type at = 0;
11952 while (at <= argline.size()) {
11953 const std::string::size_type comma = argline.find(
',', at);
11954 toks.push_back(argline.substr(
11955 at, comma == std::string::npos ? std::string::npos : comma - at));
11956 if (comma == std::string::npos)
break;
11959 std::string result;
11960 if (toks.size() < 2) {
11961 result =
"line-cli: the argument list needs at least two tokens; the first two "
11962 "are replaced by --file and the staged model path\n";
11964 toks[0] =
"--file";
11966 std::vector<char*> argv;
11967 std::vector<std::string> store;
11968 store.push_back(
"line-cli");
11969 for (std::size_t i = 0; i < toks.size(); ++i) store.push_back(toks[i]);
11970 for (std::size_t i = 0; i < store.size(); ++i)
11971 argv.push_back(
const_cast<char*
>(store[i].c_str()));
11974 Options ro = parse_args(
static_cast<int>(argv.size()), &argv[0]);
11982 result = std::string(
"line-cli: ") + e.what() +
"\n";
11985 std::remove(path.c_str());
12014 if (!spec.is_object())
12016 "{\"kind\":\"network\",\"queues\":3,\"closedClasses\":1}");
12017 const std::string kind = gen_str(spec,
"kind",
"network");
12018 if (kind ==
"network") {
12019 static const char*
const keys[] = {
12020 "kind",
"seed",
"name",
"queues",
"delays",
"openClasses",
"closedClasses",
12021 "schedStrat",
"routingStrat",
"distribution",
"cclassJobLoad",
"varyingServiceRates",
12022 "multiServerQueues",
"randomCSNodes",
"multiChainCS",
"topology"};
12023 gen_check_keys(spec, keys,
sizeof(keys) /
sizeof(keys[0]));
12025 if (spec.contains(
"seed")) g.
set_seed(gen_int(spec,
"seed", 0));
12035 const std::string topo = gen_str(spec,
"topology",
"rand");
12036 if (topo ==
"rand")
12038 else if (topo ==
"cyclic")
12042 "'; accepted forms are: rand, cyclic");
12045 const long nq = gen_int(spec,
"queues", 1);
12046 const long nd = gen_int(spec,
"delays", -1);
12047 const long no = gen_int(spec,
"openClasses", 0);
12048 const long nc = gen_int(spec,
"closedClasses", 1);
12050 static_cast<int>(no),
static_cast<int>(
nc));
12054 if (kind ==
"layered") {
12055 static const char*
const keys[] = {
12056 "kind",
"seed",
"name",
"clients",
"levels",
"tasks",
"processors",
"populationRange",
12057 "thinkTimeRange",
"taskMultiRange",
"procMultiRange",
"hostDemandRange",
12058 "synchCallRange",
"taskInfProbability",
"procInfProbability"};
12059 gen_check_keys(spec, keys,
sizeof(keys) /
sizeof(keys[0]));
12061 if (spec.contains(
"seed")) g.
set_seed(gen_int(spec,
"seed", 0));
12071 const long ncl = gen_int(spec,
"clients", 1);
12072 const long nlv = gen_int(spec,
"levels", 1);
12073 const long ntk = gen_int(spec,
"tasks", 1);
12074 const long npr = gen_int(spec,
"processors", 1);
12076 g.
generate(
static_cast<int>(ncl),
static_cast<int>(nlv),
static_cast<int>(ntk),
12077 static_cast<int>(npr));
12085 "'; accepted forms are: network, layered");
12099int dispatch(
const Options& in,
bool allow_server,
bool no_args) {
12104 if (!o.knobs.verbosity.empty()) g_verbosity = o.knobs.verbosity;
12111 : (g_verbosity ==
"debug" || g_verbosity ==
"verbose")
12118 if (o.help || no_args) {
12119 print_brief_help();
12123 std::printf(
"line-cli %s\n", kVersion);
12146 "-p runs the solver as a server, which this caller did not permit");
12150 if (!o.file.empty())
12152 "-p runs the solver as a server, where each request carries its own model; "
12153 "-f names a model on the command line and the two cannot both be the source");
12165 reset_invocation_state(
false);
12170 std::vector<std::string> docs;
12177 std::vector<std::string> store;
12178 store.push_back(
"line-cli");
12179 for (std::size_t i = 0; i < req.
argv.size(); ++i) store.push_back(req.
argv[i]);
12180 std::vector<char*> argv;
12181 for (std::size_t i = 0; i < store.size(); ++i) argv.push_back(&store[i][0]);
12195 }
catch (
const InterruptRequested& e) {
12197 res.
error = e.what();
12201 res.
error = e.what();
12203 }
catch (
const std::exception& e) {
12205 res.
error = std::string(
"unexpected failure: ") + e.what();
12212 g_json_sink =
nullptr;
12213 g_interrupt_cb =
nullptr;
12214 g_interrupt_user =
nullptr;
12216 reset_invocation_state(
false);
12222 return dispatch(parse_args(argc, argv),
true, argc == 1);
12224 std::fprintf(stderr,
"line-cli: %s\n", e.what());
12226 }
catch (
const std::exception& e) {
12227 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.
T solver_ctmc_joint(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const NetState< T > &state)
Port of solver_ctmc_joint: P(the network is in exactly state).
Matrix< T > ctmc_get_state_space_aggr(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getStateSpaceAggr.m: the per-(station, class) job counts of every state,...
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
CtmcAnySolution< T > solver_ctmc_analyzer_any(const NetworkStruct< T > &sn, const CtmcOptions &opt)
The entry point a caller who does not know which path a model needs should use: pick the WAITQ walk w...
CtmcTransient< T > solver_ctmc_transient_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, const std::vector< T > &grid=std::vector< T >())
Port of solver_ctmc_transient_analyzer.m.
CtmcTranProb< T > ctmc_get_tran_prob(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr, std::size_t ind)
Port of @@SolverCTMC/getTranProb.m: pi(t) over the whole chain, labelled by one node's local state.
CtmcFirstPassage ctmc_cdf_firstpasst(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, const std::string &method="expm")
Port of @@SolverCTMC/getCdfFirstPassT.m: the distribution of the FIRST PASSAGE TIME from state set A ...
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
CtmcTranProb< T > ctmc_get_tran_prob_aggr(const NetworkStruct< T > &sn, const CtmcTransient< T > &tr, std::size_t ind)
Port of @@SolverCTMC/getTranProbAggr.m: pi(t), labelled by one node's per-class job counts.
std::vector< std::vector< T > > solver_ctmc_tran_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, std::vector< T > *tout=nullptr, std::vector< std::string > *names=nullptr)
Port of @@SolverCTMC/getTranReward: E[r(X(t))] = sum_s pi_t(s) r(s).
CtmcReward< T > solver_ctmc_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, std::size_t tmax=1000)
Port of solver_ctmc_reward.m.
CtmcFirstPassageMoments< T > ctmc_firstpasst_moments(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, std::size_t nmax=3)
Port of @@SolverCTMC/getFirstPassTMoments.m: moments of order 1..nmax of the first passage time from ...
Matrix< T > solver_ctmc_sample_sys_aggr(const NetworkStruct< T > &sn, const CtmcSamplePath< T > &path)
Port of @@SolverCTMC/sampleSysAggr: the same walk, reported as per-(station, class) job counts rather...
EnvStatevecSolution< T > solver_env_statevec(Environment< T > &e, const EnvStatevecOptions< T > &o)
Solve in one call, for a caller with no use for the solver object.
EnvAnalyzerSolution< T > solver_env(Environment< T > &e, const EnvOptions &o)
SolverENV.init's analyzer selection: solve the environment with the coupling o.method names.
FluidKpTransient solver_fluid_tran_avg_var(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of @@SolverFLD/getTranAvgVar: the queue-length VARIANCE along the trajectory,...
double aoi_cdf(const AoiMe &me, double t)
getCdfAoI: F(t) = 1 - S(t) with S the survival function of the age law.
double fluid_prob_aggr(const qn::NetworkStruct< T > &sn, const FluidSolution &sol, std::size_t ist, double *logp_out=nullptr)
Port of @@SolverFLD/getProbAggr: the probability that station ist holds the marginal population of th...
AoiTopology aoi_is_aoi(const qn::NetworkStruct< T > &sn)
Port of aoi_is_aoi.m.
std::string solver_fluid_export_odes(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const std::string ¬ation="scalar", const std::string &model_name="model")
Port of @@SolverFLD/exportODEs.m at the runner's own method resolution, so the exported system is the...
FluidJacobian fluid_jacobian(const FluidSymSystem &sys, const FluidSymbolicOptions &opt=FluidSymbolicOptions())
Jacobian, drift and equilibria of the mean-field vector field.
FluidSolution solver_fluid_run_analyzer(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, qn::NetworkStruct< T > *sn_out=nullptr, qn::NetworkStruct< T > *refreshed_out=nullptr, solvers::CacheMetrics< T > *cache_out=nullptr)
Port of @@SolverFLD/runAnalyzer.m: resolve the method, route to the function the reference routes to,...
std::vector< std::vector< FluidPassage > > solver_fluid_cdf_respt(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=201)
Port of @@SolverFLD/getCdfRespT: the response-time law of every (station, class) pair,...
FluidSymSystem fluid_symodes(const qn::NetworkStruct< T > &sn, const std::string &method_in, double pstar, const std::vector< double > &init_sol)
Build the symbolic system of sn under opt.method.
std::vector< FluidTranPoint > solver_fluid_run_transient(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=101)
-a tran / @@SolverFLD/getTranAvg with the method HONOURED, which is the one place the reference does ...
Response get(const std::string &url, int timeoutMillis)
GET a URL.
bool is_layered_json(const std::string &path)
True when a file is a LayeredNetwork model.json rather than an .lqnx.
std::string jsim_stage_stdin(const std::string &text)
Write a piped XML model document to a temporary file and return its path.
qn::Network< T > read_network_json(const std::string &path)
Parse a model.json file into a qn::Network<T>.
qn::Network< T > read_jsim(const std::string &path, const std::string &name=std::string())
Read a JSIM document into a Network.
lqn::LqnStruct< T > read_layered_model(const std::string &path)
Read a layered model from either interchange: the LINE model.json or the LQNS .lqnx.
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.
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....
SsaProbReport solver_ssa_prob(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt)
The whole -a prob report over the model's DEFAULT INITIAL STATE, which is the state SolverCTMC's own ...
SsaSamplePath< T > ssa_sample_node(const qn::NetworkStruct< T > &sn, const SsaSerialRun< T > &r, std::size_t ind)
sample(node) and sampleAggr(node): the same trajectory, one node's block.
SsaSamplePath< T > ssa_sample_sys(const qn::NetworkStruct< T > &sn, const SsaSerialRun< T > &r)
sampleSys and sampleSysAggr: the trajectory itself.
SsaSolution solver_ssa(const qn::NetworkStruct< T > &sn, const SsaOptions &opt, std::vector< SsaCacheRatio > *cache=nullptr)
@@SolverSSA/runAnalyzer itself: the engine the method selects, then the result assembly the reference...
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....
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.
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.