5#ifndef LINE_SOLVERS_WRAPPERS_JMT_SOLVER_JMT_H
6#define LINE_SOLVERS_WRAPPERS_JMT_SOLVER_JMT_H
100static const char*
JMT_JAR_URL =
"https://line-solver.sourceforge.net/latest/JMT.jar";
118 "932cf6b34b04f9ba5b7c3ba0b07dc6c9fa80aa816ffcad7a39ac5d20a31bb9cd";
142inline bool is_file(
const std::string& p) {
144 return !p.empty() && ::stat(p.c_str(), &st) == 0 && S_ISREG(st.st_mode);
147inline bool is_exec(
const std::string& p) {
149 return !p.empty() && ::stat(p.c_str(), &st) == 0 && ::access(p.c_str(), X_OK) == 0;
152inline bool is_dir(
const std::string& p) {
154 return !p.empty() && ::stat(p.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
157inline std::string env_or_empty(
const char* name) {
158 const char* v = std::getenv(name);
159 return v !=
nullptr ? std::string(v) : std::string();
162inline std::string exe_dir() {
164 const ssize_t n = ::readlink(
"/proc/self/exe", buf,
sizeof(buf) - 1);
165 if (n <= 0)
return std::string();
166 std::string p(buf,
static_cast<std::size_t
>(n));
167 const std::size_t s = p.find_last_of(
'/');
168 return s == std::string::npos ? std::string() : p.substr(0, s);
171inline std::string cwd() {
173 return ::getcwd(buf,
sizeof(buf)) !=
nullptr ? std::string(buf) : std::string();
193inline std::string private_tmpdir_flag(
const std::string& model_path) {
194 const std::size_t s = model_path.find_last_of(
'/');
195 const std::string dir = s == std::string::npos ? cwd() : model_path.substr(0, s);
196 return "-Djava.io.tmpdir=" + (dir.empty() ? cwd() : dir);
211inline std::string jmt_jar_path() {
212 const std::string forced = env_or_empty(
"LINE_JMT_DIR");
213 if (!forced.empty())
return is_file(forced +
"/JMT.jar") ? forced : std::string();
214 const std::string roots[2] = {exe_dir(), cwd()};
215 for (
int r = 0; r < 2; ++r) {
216 if (roots[r].empty())
continue;
217 std::string dir = roots[r];
218 for (
int k = 0; k < 7; ++k) {
219 if (is_file(dir +
"/JMT.jar"))
return dir;
220 if (is_file(dir +
"/common/JMT.jar"))
return dir +
"/common";
224 return std::string();
228inline std::string which_on_path(
const std::string& name) {
229 const std::string path = env_or_empty(
"PATH");
231 while (b <= path.size()) {
232 const std::size_t e = path.find(
':', b);
233 const std::string dir =
234 path.substr(b, e == std::string::npos ? std::string::npos : e - b);
235 if (!dir.empty() && is_exec(dir +
"/" + name))
return dir +
"/" +
name;
236 if (e == std::string::npos)
break;
239 return std::string();
243inline std::string find_java() {
244 const std::string forced = env_or_empty(
"LINE_JAVA");
245 if (!forced.empty())
return is_exec(forced) ? forced : std::string();
246 const std::string home = env_or_empty(
"JAVA_HOME");
247 if (!home.empty() && is_exec(home +
"/bin/java"))
return home +
"/bin/java";
248 return which_on_path(
"java");
252inline bool docker_consented() {
253 const std::string v = env_or_empty(
"LINE_JMT_DOCKER");
254 return v ==
"1" || v ==
"true" || v ==
"TRUE" || v ==
"yes" || v ==
"y" || v ==
"Y";
266inline bool download_consented() {
267 const std::string v = env_or_empty(
"LINE_JMT_DOWNLOAD");
268 return v ==
"1" || v ==
"true" || v ==
"TRUE" || v ==
"yes" || v ==
"y" || v ==
"Y";
281inline std::string jmt_download_dir() {
282 const std::string forced = env_or_empty(
"LINE_JMT_DIR");
283 if (!forced.empty())
return forced;
284 const std::string roots[2] = {exe_dir(), cwd()};
285 for (
int r = 0; r < 2; ++r) {
286 if (roots[r].empty())
continue;
287 std::string dir = roots[r];
288 for (
int k = 0; k < 7; ++k) {
289 if (is_dir(dir +
"/common"))
return dir +
"/common";
312inline std::string find_fetcher() {
313 const std::string curl = which_on_path(
"curl");
314 return curl.empty() ? which_on_path(
"wget") : curl;
317inline bool fetch_url(
const std::string& tool,
const std::string& url,
const std::string& dest) {
318 const bool is_curl = tool.size() >= 4 && tool.compare(tool.size() - 4, 4,
"curl") == 0;
320 std::vector<std::string> argv;
321 argv.push_back(tool);
322 argv.push_back(
"-fL");
323 argv.push_back(
"--proto");
324 argv.push_back(
"=https");
325 argv.push_back(
"--proto-redir");
326 argv.push_back(
"=https");
327 argv.push_back(
"--tlsv1.2");
328 argv.push_back(
"--connect-timeout");
329 argv.push_back(
"30");
330 argv.push_back(
"--max-time");
331 argv.push_back(
"1800");
332 argv.push_back(
"-o");
333 argv.push_back(dest);
337 if (tool.empty())
return false;
338 std::vector<std::string> argv;
339 argv.push_back(tool);
340 argv.push_back(
"--https-only");
341 argv.push_back(
"--timeout=30");
342 argv.push_back(
"--tries=2");
343 argv.push_back(
"-O");
344 argv.push_back(dest);
350inline std::string expected_jar_sha256() {
351 std::string want =
util::trim(env_or_empty(
"LINE_JMT_SHA256"));
353 for (std::size_t i = 0; i < want.size(); ++i)
354 want[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(want[i])));
374inline std::string jmt_download_jar() {
375 if (!download_consented())
return std::string();
377 std::string url = env_or_empty(
"LINE_JMT_URL");
379 if (url.compare(0, 8,
"https://") != 0)
380 throw InputError(
"SolverJMT: refusing to fetch JMT.jar over '" + url +
381 "': the download must be https, so that the transfer is authenticated "
382 "before the digest authenticates the file");
384 const std::string dir = jmt_download_dir();
385 if (!is_dir(dir) || ::access(dir.c_str(), W_OK) != 0)
386 throw InputError(
"SolverJMT: cannot write JMT.jar to '" + dir +
387 "': the directory does not exist or is not writable. Set LINE_JMT_DIR to "
388 "a directory that is");
390 const std::string tool = find_fetcher();
392 throw InputError(
"SolverJMT: LINE_JMT_DOWNLOAD is set but neither curl nor wget is on "
393 "PATH, and this port has no TLS client of its own. Install one, or place "
397 const std::string dest = dir +
"/JMT.jar";
398 const std::string part = dest +
".part-" + std::to_string(
static_cast<long>(::getpid()));
399 ::unlink(part.c_str());
400 std::printf(
"\nJava Modelling Tools cannot be found. Fetching %s (about 31 MiB) into %s\n",
401 url.c_str(), dir.c_str());
403 if (!fetch_url(tool, url, part)) {
404 ::unlink(part.c_str());
405 throw InputError(
"SolverJMT: the download of " + url +
" failed; see the transfer's own "
409 const std::string want = expected_jar_sha256();
413 const long long bytes = ::stat(part.c_str(), &st) == 0 ?
static_cast<long long>(st.st_size)
415 ::unlink(part.c_str());
417 "SolverJMT: the jar fetched from " + url +
" is not the one this port pins.\n" +
418 " expected sha256 " + want +
"\n" +
" received sha256 " +
419 (got.empty() ? std::string(
"<unreadable>") : got) +
" (" + std::to_string(bytes) +
421 "The file has been discarded. That URL serves a rolling 'latest' build, so an upstream "
422 "republication looks exactly like this: check the digest against the publisher and, if "
423 "it is the build you want, set LINE_JMT_SHA256 to it");
426 if (::rename(part.c_str(), dest.c_str()) != 0) {
427 ::unlink(part.c_str());
428 throw InputError(
"SolverJMT: could not move the verified jar into place at '" + dest +
"'");
430 std::printf(
"Verified sha256 %s and installed %s\n", got.c_str(), dest.c_str());
435inline std::string read_file(
const std::string& path) {
436 std::ifstream f(path.c_str(), std::ios::binary);
437 if (!f)
throw InputError(
"SolverJMT: cannot read '" + path +
"'");
438 std::ostringstream ss;
443inline void write_file(
const std::string& path,
const std::string& text) {
444 std::ofstream f(path.c_str(), std::ios::binary);
445 if (!f)
throw InputError(
"SolverJMT: cannot write '" + path +
"'");
446 f.write(text.data(),
static_cast<std::streamsize
>(text.size()));
447 if (!f)
throw InputError(
"SolverJMT: write failed on '" + path +
"'");
451inline std::string json_escape(
const std::string& s) {
453 out.reserve(s.size() + 16);
454 for (std::size_t i = 0; i < s.size(); ++i) {
455 const unsigned char c =
static_cast<unsigned char>(s[i]);
457 case '"': out +=
"\\\"";
break;
458 case '\\': out +=
"\\\\";
break;
459 case '\n': out +=
"\\n";
break;
460 case '\r': out +=
"\\r";
break;
461 case '\t': out +=
"\\t";
break;
465 std::snprintf(buf,
sizeof(buf),
"\\u%04x", c);
484inline std::string json_string_field(
const std::string& body,
const std::string& key) {
485 const std::string needle =
"\"" + key +
"\"";
486 std::size_t p = body.find(needle);
487 if (p == std::string::npos)
return std::string();
488 p = body.find(
':', p + needle.size());
489 if (p == std::string::npos)
return std::string();
491 while (p < body.size() && (body[p] ==
' ' || body[p] ==
'\t' || body[p] ==
'\n')) ++p;
492 if (p >= body.size() || body[p] !=
'"')
return std::string();
495 while (p < body.size() && body[p] !=
'"') {
496 if (body[p] ==
'\\' && p + 1 < body.size()) {
499 case 'n': out.push_back(
'\n');
break;
500 case 'r': out.push_back(
'\r');
break;
501 case 't': out.push_back(
'\t');
break;
503 if (p + 4 < body.size()) {
504 const long cp = std::strtol(body.substr(p + 1, 4).c_str(),
nullptr, 16);
505 if (cp < 0x80) out.push_back(
static_cast<char>(cp));
510 default: out.push_back(body[p]);
513 out.push_back(body[p]);
525inline util::ProcResult
jmt_run_docker(
const std::string& image,
const std::string& mode,
526 const std::string& model_path,
long seed,
531 if (mode ==
"sim")
return "jsim";
532 if (mode ==
"mva")
return "jmva";
533 throw InputError(
"SolverJMT: unknown JMT analysis mode '" + mode +
"'");
538 return !detail::jmt_jar_path().empty() && !detail::find_java().empty();
546 const std::string& model_path,
long seed,
548 std::string url = rest_url;
549 while (!url.empty() && url[url.size() - 1] ==
'/') url.erase(url.size() - 1);
551 if (url.size() < 8 || url.substr(url.size() - 8) !=
"/solve/" + mode.substr(0, 1))
552 if (url.find(
"/api/v") == std::string::npos) url +=
"/api/v1/solve/" + mode;
554 std::string body =
"{\"model\":{\"content\":\"" +
555 detail::json_escape(detail::read_file(model_path)) +
556 "\",\"base64\":false}";
560 if (mode ==
"sim") body +=
",\"options\":{\"seed\":" + std::to_string(seed) +
"}";
565 throw InputError(
"SolverJMT: the JMT REST server at " + url +
" answered HTTP " +
566 std::to_string(resp.
status));
567 const std::string status = detail::json_string_field(resp.
body,
"status");
568 if (status !=
"completed") {
569 const std::string err = detail::json_string_field(resp.
body,
"error");
570 throw InputError(
"SolverJMT: the JMT REST solve failed: " +
571 (err.empty() ? std::string(
"unspecified error") : err));
573 const std::string
xml = detail::json_string_field(resp.
body,
"result_xml");
576 "SolverJMT: the JMT REST response carries no result document. The server was asked "
577 "to include the raw output; check that include_raw_output is not disabled");
581 r.
out = detail::json_string_field(resp.
body,
"stdout");
596 std::string jar_dir = detail::jmt_jar_path();
597 const std::string java = detail::find_java();
602 if (jar_dir.empty() && !java.empty()) jar_dir = detail::jmt_download_jar();
603 if (!jar_dir.empty() && !java.empty()) {
604 std::vector<std::string> argv;
605 argv.push_back(java);
606 argv.push_back(detail::private_tmpdir_flag(model_path));
607 argv.push_back(
"-cp");
608 argv.push_back(jar_dir +
"/JMT.jar");
609 argv.push_back(
"jmt.commandline.Jmt");
610 argv.push_back(mode);
611 argv.push_back(model_path);
612 argv.push_back(
"-seed");
613 argv.push_back(std::to_string(seed));
614 argv.push_back(
"--illegal-access=permit");
623 std::to_string(
opt.timeout) +
"s and was killed");
629 std::string image =
opt.container.empty() ? detail::env_or_empty(
"LINE_JMT_IMAGE")
640 const std::string found =
641 std::string(java.empty() ?
"no Java runtime (LINE_JAVA, JAVA_HOME or PATH)" :
"a Java runtime") +
642 (jar_dir.empty() ?
" and no JMT.jar (LINE_JMT_DIR, else JMT.jar or common/JMT.jar in the "
643 "executable's or working directory or up to 6 levels above)"
644 :
" and JMT.jar in " + jar_dir);
646 "SolverJMT: no way to reach JMT: found " + found +
". Install a Java runtime and put "
647 "JMT.jar in common/ (" + std::string(
JMT_JAR_URL) +
648 ") or in the directory LINE_JMT_DIR names, or set LINE_JMT_DOWNLOAD=1 to let curl or wget "
649 "fetch and verify that jar for you, or "
650 "point options.rest_url at a JMT REST server, or set LINE_JMT_DOCKER=1 to allow the " +
651 image +
" image to be pulled and used");
665 const std::string& model_path,
long seed,
668 const std::size_t slash = model_path.find_last_of(
'/');
669 const std::string base =
670 slash == std::string::npos ? model_path : model_path.substr(slash + 1);
671 const std::string staged = work.
path() +
"/" + base;
672 detail::write_file(staged, detail::read_file(model_path));
674 const std::string uid = std::to_string(
static_cast<long>(::getuid()));
675 const std::string gid = std::to_string(
static_cast<long>(::getgid()));
676 std::vector<std::string> argv;
677 argv.push_back(
"docker");
678 argv.push_back(
"run");
679 argv.push_back(
"--rm");
680 argv.push_back(
"--user");
681 argv.push_back(uid +
":" + gid);
682 argv.push_back(
"-v");
683 argv.push_back(work.
path() +
":" + work.
path());
684 argv.push_back(
"-w");
685 argv.push_back(work.
path());
686 argv.push_back(image);
687 argv.push_back(mode);
688 argv.push_back(base);
689 argv.push_back(
"-seed");
690 argv.push_back(std::to_string(seed));
694 std::to_string(
opt.timeout) +
"s and was killed");
696 const std::string produced = staged +
"-result." +
jmt_result_ext(mode);
697 if (detail::is_file(produced))
698 detail::write_file(model_path +
"-result." +
jmt_result_ext(mode),
699 detail::read_file(produced));
720 const std::string& command_output) {
721 if (!detail::is_file(result_path)) {
723 "SolverJMT: JMT did not output a result file, the simulation has likely failed.";
724 if (!command_output.empty()) msg +=
" JMT output: " + command_output;
727 const std::unique_ptr<xml::Element> doc =
xml::parse_file(result_path);
728 std::vector<JmtMeasure> out;
729 const std::vector<const xml::Element*> ms = doc->by_tag(
"measure");
730 for (std::size_t i = 0; i < ms.size(); ++i) {
737 m.
mean = std::strtod(e->
attr(
"meanValue").c_str(),
nullptr);
741 m.
lower = std::strtod(e->
attr(
"lowerLimit").c_str(),
nullptr);
742 m.
upper = std::strtod(e->
attr(
"upperLimit").c_str(),
nullptr);
790 const std::vector<JmtMeasure>& measures,
bool confint) {
791 const std::size_t M =
sn.nstations, K =
sn.nclasses, F =
sn.regions.size();
793 const double nan = std::numeric_limits<double>::quiet_NaN();
815 for (std::size_t f = 0; f < F; ++f)
816 for (std::size_t r = 0; r < K; ++r) {
821 std::map<std::string, std::size_t> station_of, class_of;
822 for (std::size_t i = 1; i <=
sn.nodes.size(); ++i) station_of[
sn.nodes[i - 1].name] = i;
823 for (std::size_t r = 1; r <= K; ++r) class_of[
sn.classes[r - 1].name] = r;
826 std::vector<double> chainpop(K, 0.0);
827 for (std::size_t c = 0; c <
sn.inchain.size(); ++c) {
829 for (std::size_t r :
sn.inchain[c]) {
830 const double n =
sn.classes[r - 1].population;
831 if (std::isfinite(n)) tot += n;
833 for (std::size_t r :
sn.inchain[c]) chainpop[r - 1] = tot;
837 std::map<std::pair<std::size_t, std::size_t>,
double> fj_respt, fj_qlen;
839 for (std::size_t i = 0; i < measures.size(); ++i) {
845 const auto ni = station_of.find(m.
station);
846 const auto ci = class_of.find(m.
job_class);
847 if (ni != station_of.end() && ci != class_of.end()) {
848 const std::pair<std::size_t, std::size_t> key(ni->second, ci->second);
850 fj_respt[key] = m.
mean;
852 fj_qlen[key] = m.
mean;
861 if (is_fcr && m.
station.compare(0, 8,
"FCRegion") == 0) {
862 const long f = std::strtol(m.
station.substr(8).c_str(),
nullptr, 10);
863 if (f < 1 ||
static_cast<std::size_t
>(f) > F)
continue;
864 const std::size_t row = M +
static_cast<std::size_t
>(f) - 1;
870 const double per_class = m.
mean /
static_cast<double>(K);
871 for (std::size_t r = 0; r < K; ++r) {
891 const auto ni = station_of.find(m.
station);
892 const auto ci = class_of.find(m.
job_class);
893 if (ni == station_of.end() || ci == class_of.end())
continue;
895 const auto cp =
sn.nodeparam.find(ni->second);
896 if (cp ==
sn.nodeparam.end())
continue;
898 if (hit.empty()) hit.assign(K, zero);
901 for (std::size_t r = 0; r < cp->second.hitclass.size(); ++r)
902 if (cp->second.hitclass[r] == ci->second)
910 const auto ci = class_of.find(m.
job_class);
911 if (ci == class_of.end())
continue;
912 const std::size_t r = ci->second;
913 const bool open = !std::isfinite(
sn.classes[r - 1].population);
920 const auto ni = station_of.find(m.
station);
921 const auto ci = class_of.find(m.
job_class);
922 if (ni == station_of.end() || ci == class_of.end())
continue;
923 const std::size_t ist =
sn.nodes[ni->second - 1].station;
924 if (ist == 0)
continue;
925 const std::size_t r = ci->second;
926 const bool open = !std::isfinite(
sn.classes[r - 1].population);
932 res.
avg.QN(ist - 1, r - 1) = v;
933 if (confint) res.
QCI(ist - 1, r - 1) = ci_v;
935 res.
avg.UN(ist - 1, r - 1) = v;
936 if (confint) res.
UCI(ist - 1, r - 1) = ci_v;
938 res.
avg.RN(ist - 1, r - 1) = v;
939 if (confint) res.
RCI(ist - 1, r - 1) = ci_v;
941 res.
avg.TN(ist - 1, r - 1) = v;
942 if (confint) res.
TCI(ist - 1, r - 1) = ci_v;
944 res.
avg.AN(ist - 1, r - 1) = v;
945 if (confint) res.
ACI(ist - 1, r - 1) = ci_v;
947 res.
Tard(ist - 1, r - 1) = v;
948 if (confint) res.
TardCI(ist - 1, r - 1) = ci_v;
957 for (std::size_t i = 0; i <
sn.nstations; ++i) {
959 for (std::size_t r = 0; r < K; ++r) {
960 res.
avg.UN(i, r) = res.
avg.QN(i, r);
961 if (confint) res.
UCI(i, r) = res.
QCI(i, r);
969 for (std::size_t p = 0; p < fjm.
npairs(); ++p) {
970 const std::size_t jst =
sn.nodes[fjm.
joins[p] - 1].station;
971 for (std::size_t r = 1; r <= K; ++r) {
972 const std::pair<std::size_t, std::size_t> key(fjm.
forks[p], r);
973 const auto rt = fj_respt.find(key);
974 if (rt == fj_respt.end() || !std::isfinite(rt->second))
continue;
976 const auto ql = fj_qlen.find(key);
977 if (ql != fj_qlen.end() && std::isfinite(ql->second))
979 if (jst != 0 && jst <= M) fjm.
Tput(p, r - 1) = res.
avg.TN(jst - 1, r - 1);
997 const std::size_t M =
sn.nstations, K =
sn.nclasses, F =
sn.regions.size();
1003 for (std::size_t f = 0; f < F; ++f) {
1005 for (std::size_t r = 1; r <= K; ++r) {
1010 double carried = 0.0;
1011 for (std::size_t ii = 1; ii <= M; ++ii) {
1013 for (std::size_t j = 1; j <= M; ++j) {
1015 for (std::size_t rp = 1; rp <= K; ++rp) {
1016 const std::size_t a = (j - 1) * K + (rp - 1);
1017 const std::size_t b = (ii - 1) * K + (r - 1);
1018 if (
sn.rt.rows() <= a ||
sn.rt.cols() <= b)
continue;
1022 const std::size_t ao = (ii - 1) * K + (r - 1), bo = (j - 1) * K + (rp - 1);
1023 if (ao <
sn.rt.rows() && bo <
sn.rt.cols())
1059 const std::string& command_output) {
1060 if (!detail::is_file(result_path)) {
1062 "SolverJMT: JMT did not output a result file, the analysis has likely failed.";
1063 if (!command_output.empty()) msg +=
" JMT output: " + command_output;
1066 const std::size_t M =
sn.nstations, K =
sn.nclasses, C =
sn.nchains;
1078 std::map<std::string, std::size_t> station_of;
1079 for (std::size_t i = 1; i <= M; ++i)
1080 station_of[
sn.nodes[
sn.station_to_node[i - 1] - 1].name] = i;
1082 const std::unique_ptr<xml::Element> doc =
xml::parse_file(result_path);
1084 const std::vector<const xml::Element*>
nc = doc->by_tag(
"normconst");
1087 std::numeric_limits<double>::quiet_NaN())
1089 std::strtod(
nc[0]->attr(
"logValue").c_str(),
nullptr));
1093 if (
sn.sourceIdx != 0)
1094 for (std::size_t r = 0; r < K; ++r)
1095 if (!
sn.disabled[
sn.sourceIdx - 1][r])
1096 res.
avg.TN(
sn.sourceIdx - 1, r) =
sn.rates(
sn.sourceIdx - 1, r);
1098 const std::vector<const xml::Element*> blocks = doc->by_tag(
"stationresults");
1099 for (std::size_t b = 0; b < blocks.size(); ++b) {
1100 const auto si = station_of.find(blocks[b]->attr(
"station"));
1101 if (si == station_of.end())
continue;
1102 const std::size_t i = si->second;
1106 double ns =
sn.stations[i - 1].nservers;
1107 for (
const T& s :
sn.stations[i - 1].lldscaling)
1109 const std::vector<const xml::Element*> crs = blocks[b]->by_tag(
"classresults");
1110 for (std::size_t c = 1; c <= crs.size() && c <= C; ++c) {
1111 const std::vector<const xml::Element*> ms = crs[c - 1]->by_tag(
"measure");
1118 double chain_tput = std::numeric_limits<double>::quiet_NaN();
1119 for (std::size_t m = 0; m < ms.size(); ++m) {
1120 if (ms[m]->attr(
"measureType") ==
"Throughput") {
1121 const std::string traw = ms[m]->attr(
"meanValue");
1122 chain_tput = traw ==
"NaN" ? std::numeric_limits<double>::quiet_NaN()
1123 : std::strtod(traw.c_str(),
nullptr);
1127 for (std::size_t m = 0; m < ms.size(); ++m) {
1128 const std::string kind = ms[m]->attr(
"measureType");
1129 const std::string raw = ms[m]->attr(
"meanValue");
1130 const double val = raw ==
"NaN" ? std::numeric_limits<double>::quiet_NaN()
1131 : std::strtod(raw.c_str(),
nullptr);
1133 const double vchain_ref =
1136 for (std::size_t k :
sn.inchain[c - 1]) {
1140 if (kind ==
"Utilization") {
1141 if (vchain_ref == 0.0)
continue;
1142 v = stk * chain_tput / vchain_ref * al;
1143 if (std::isfinite(ns)) v /= ns;
1145 }
else if (kind ==
"Throughput") {
1147 }
else if (kind ==
"Number of Customers") {
1148 if (stchain == 0.0 || vchain_ref == 0.0)
continue;
1149 res.
avg.QN(i - 1, k - 1) =
1151 }
else if (kind ==
"Residence time" || kind ==
"Residence Time") {
1152 if (stchain == 0.0 || vchain_ref == 0.0)
continue;
1153 double visits = 0.0;
1154 if (c - 1 <
sn.visits.size()) {
1155 const std::size_t sfi =
sn.stateful_of_station(i);
1156 if (sfi != 0 &&
sn.visits[c - 1].rows() >= sfi)
1159 if (visits == 0.0)
continue;
1160 v = (val / visits) * stk / stchain / vchain_ref * al;
1183 return ::line::method_registry::methods(
"JMT");
1193 if (method ==
"replication")
1194 return "SolverJMT: the 'replication' method was removed: use a finite timespan with "
1195 "options.config.replications";
1196 return std::string();
1232 if (method.compare(0, 4,
"jmva") != 0 && method.compare(0, 8,
"jmt.jmva") != 0)
return std::string();
1234 return method +
" requires a product-form model, but a FCFS station serves its classes with "
1235 "different mean service times, and the JMVA engine would answer for the "
1236 "processor-sharing station with the same demands. Use SolverJMT 'jsim', SolverMVA "
1237 "(whose FCFS correction handles it), SolverCTMC or SolverLDES.";
1239 return "The JMVA document has no fork or join element, so the analytical engine would "
1240 "solve the model without the synchronisation. Use a simulator (SolverJMT 'jsim', "
1241 "SolverLDES) or SolverMVA, which approximates fork-join.";
1242 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1244 const char* sname = sc == qn::SchedStrategy::FCFS ?
"FCFS"
1245 : sc == qn::SchedStrategy::SIRO ?
"SIRO"
1246 : sc == qn::SchedStrategy::LCFS ?
"LCFS" :
nullptr;
1247 if (sname ==
nullptr)
continue;
1248 for (std::size_t r = 0; r <
sn.nclasses; ++r) {
1251 if (!(std::isfinite(rate) && rate > 0.0 && std::isfinite(scv)))
continue;
1254 std::snprintf(buf,
sizeof(buf),
"%.4g", scv);
1255 return "Station " +
sn.stations[i].name +
" serves class " +
sn.classes[r].name +
" under " +
1256 sname +
" with a non-exponential law (SCV " + buf +
"). The JMVA document carries "
1257 "the mean demand only, so the analytical engine would answer for the exponential "
1258 "law. Use a simulator (SolverJMT 'jsim', SolverLDES), SolverMVA, whose AMVA "
1259 "corrections read the SCV, or SolverCTMC or SolverSSA.";
1262 return std::string();
1271 if (!removed.empty())
return removed;
1275 if (!doc.empty())
return doc;
1278 for (
const auto& rg :
sn.regions)
1279 if (rg.lincon_A.rows() > 0)
1280 return "SolverJMT does not support linear admission constraints on a finite capacity region (setLinearConstraints): no JMT region carries them, so the region would be simulated without its A*n <= b limit. Use SolverCTMC, SolverNC or SolverLDES.";
1286 for (std::size_t i = 0; i <
sn.nstations; ++i)
1289 return std::string(
"SolverJMT does not support ") +
1291 " polling: JMT's polling server for it reports a queue length that grows "
1292 "without bound and a throughput below the arrival rate on a stable station. "
1293 "Use SolverMVA, SolverLDES or SolverSSA; JMT simulates GATED polling correctly.";
1300 if (
sn.has_immediate_feedback())
1301 return "SolverJMT: JMT has no immediate-feedback semantics (a job re-entering service "
1302 "while HOLDING the server); use SolverCTMC, SolverSSA or SolverLDES";
1304 double maxFinite = 0.0;
1305 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1306 const double c =
static_cast<double>(
sn.stations[i].nservers);
1307 if (std::isfinite(c)) maxFinite = std::max(maxFinite, c);
1309 if (maxFinite > 1.0)
return "SolverJMT: " + method +
" does not support multi-server stations";
1317 const std::vector<double> comom_njobs =
sn.njobs();
1318 const bool comom_closed = std::all_of(comom_njobs.begin(), comom_njobs.end(),
1319 [](
double n) { return std::isfinite(n); });
1320 if ((method ==
"jmva.comom" || method ==
"jmt.jmva.comom") &&
sn.nchains > 1 && comom_closed)
1321 return method +
" is refused on a model with " + std::to_string(
sn.nchains) +
1322 " chains: JMT's multiclass CoMoM engine silently re-solves a randomly perturbed model "
1323 "whenever its exact linear system is singular, and reports that as an exact CoMoM "
1324 "solution, so the answer is wrong and different on every run. Use 'jmva.recal', which "
1325 "is exact and reproducible on the same document, 'jmva'/'jmva.mva' for exact MVA, or "
1326 "SolverNC for a LINE-native normalizing constant.";
1327 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1328 const std::vector<T>& alpha =
sn.stations[i].lldscaling;
1329 if (alpha.empty())
continue;
1332 if (c == 1.0)
continue;
1333 bool ok = (c >= 1.0) && (c == std::floor(c));
1334 for (std::size_t n = 0; ok && n < alpha.size(); ++n)
1338 return "SolverJMT: station '" +
sn.stations[i].name +
1339 "' uses a load-dependent scaling that is not the multiserver encoding alpha(n) "
1340 "= min(n,c): JMT has no representation for it, since both the JSIM and the JMVA "
1341 "writer carry the scaling as a server count, and the model would be solved at "
1342 "the nominal service rate. Use SolverCTMC, SolverNC, SolverMVA or SolverSSA, "
1343 "which read sn.lldscaling directly";
1353 const bool is_jmva = method.compare(0, 4,
"jmva") == 0;
1354 const bool is_jsim = method ==
"default" || method ==
"jsim";
1355 if (is_jmva || is_jsim) {
1357 if (!cap_reason.empty())
return cap_reason;
1367 if (!deadline_reason.empty())
return deadline_reason;
1371 if (!cache_reason.empty())
return cache_reason;
1373 return std::string();
1387 if (
opt.samples < 5000.0)
opt.samples = 5000.0;
1393 if (std::find(valid.begin(), valid.end(),
opt.method) == valid.end())
1406 if (!refusal.empty())
1410 const bool is_mva =
opt.method.compare(0, 4,
"jmva") == 0;
1411 const std::string mode = is_mva ?
"mva" :
"sim";
1414 const std::string model_path = work.
path() +
"/model." + (is_mva ?
"jmva" :
"jsim");
1437 const std::string result_path = model_path +
"-result." +
jmt_result_ext(mode);
1450 for (std::size_t i = 0; i <
sn.nstations; ++i)
1451 for (std::size_t r = 0; r <
sn.nclasses; ++r) res.
avg.WN(i, r) = WNst(i, r);
1462 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1463 if (
sn.stations[i].nodetype != qn::NodeType::Source)
continue;
1464 for (std::size_t r = 0; r <
sn.nclasses; ++r)
1478 for (std::size_t i = 0; i <
sn.nstations; ++i)
1479 for (std::size_t r = 0; r <
sn.nclasses; ++r)
1487 res.
avg.actualmethod =
opt.method;
ExternalError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
static void substep(const char *fmt,...)
Write one indented progress line.
static void step(const char *fmt,...)
Write one progress line.
void keep()
Leave the directory in place, for a caller that wants to inspect it.
const std::string & path() const
The directory itself, with no trailing separator.
Docker primitives for the backends that legitimately ship an image.
The exception types the port throws.
Minimal HTTP/1.1 client, enough to talk to a line-*-rest service.
Port of @@JMTIO: a refreshed NetworkStruct written out as a JMT .jsimg simulation model.
Port of @@JMTIO/writeJMVA.m: the CHAIN-level product-form model in the JMVA interchange format.
Running progress log of a LINE solver run (the "solver console").
The method names of each solver, in listing order.
bool sn_has_multi_class_heter_fcfs(const qn::NetworkStruct< T > &sn)
sn_has_multi_class_heter_fcfs: an FCFS station whose per-class rates differ.
bool sn_has_fork_join(const qn::NetworkStruct< T > &sn)
any(sn.fj(:) > 0): the model has a Fork closed by a Join.
Response post_json(const std::string &url, const std::string &json, int timeoutMillis)
POST a JSON document.
bool docker_has_storage_for(const std::string &image)
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.
std::string jmt_buffer_capacity_refusal(const qn::NetworkStruct< T > &sn, bool jmva_engine)
JmtWriter::buffer_capacity_refusal without a document: the writer's own binding-buffer verdict,...
bool docker_daemon_available()
True if the Docker daemon is reachable.
bool docker_has_local_image(const std::string &image)
std::string jmt_cache_refusal(const qn::NetworkStruct< T > &sn)
JmtWriter::cache_refusal over every Cache node, without a document: the writer's own cache verdict,...
std::string write_jmva(const qn::NetworkStruct< T > &L, const std::string &path, const std::string &method, std::size_t samples)
Port of writeJMVA(sn, outputFileName, options).
std::string jmt_deadline_refusal(const qn::NetworkStruct< T > &sn)
Port of jmtDeadlineRefusal.m: the due date an EDD or EDF station needs and the model does not carry,...
bool docker_pull(const std::string &image)
Pulls an image, streaming Docker's progress to stdout and stderr.
static const char * JMT_DOCKER_IMAGE
The default JMT REST/Docker image, MATLAB's jmtDockerImage candidate.
void jmt_region_losses(const qn::NetworkStruct< T > &sn, JmtResult< T > &res)
The region loss table, port of the sn.nregions > 0 tail of getResults.m.
util::ProcResult jmt_run(const std::string &mode, const std::string &model_path, long seed, const JmtOptions &opt)
Port of jmtRun: one batch analysis, leaving the result where the JMT CLI itself would leave it.
JmtResult< T > jmt_parse_jmva(const qn::NetworkStruct< T > &sn, const std::string &result_path, const std::string &command_output)
Port of getResultsJMVA.m: the per-CHAIN answer spread back over the classes.
util::ProcResult jmt_run_docker(const std::string &image, const std::string &mode, const std::string &model_path, long seed, const JmtOptions &opt)
Run the analysis inside the JMT container and copy the result back beside the model,...
util::ProcResult jmt_solve_rest(const std::string &rest_url, const std::string &mode, const std::string &model_path, long seed, const JmtOptions &opt)
Port of jmtSolveRest: POST the model document, write the result document back beside the model so the...
std::vector< std::string > jmt_list_valid_methods()
Port of SolverJMT.listValidMethods.
JmtResult< T > solver_jmt_run_analyzer(const qn::NetworkStruct< T > &sn, const JmtOptions &opt_in)
Port of @@SolverJMT/runAnalyzer.m, the jsim and jmva arms.
std::string jmva_document_refusal(const qn::NetworkStruct< T > &sn, const std::string &method)
The structural half of SolverJMT's method gate; empty when admissible.
JmtResult< T > jmt_map_measures(const qn::NetworkStruct< T > &sn, const std::vector< JmtMeasure > &measures, bool confint)
Port of getResults.m: the measures mapped onto the metric matrices.
bool jmt_available()
True when a local JVM and common/JMT.jar are both present.
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.
static const char * JMT_JAR_URL
Where a missing jar is fetched from; LINE_JMT_URL names a mirror instead.
std::vector< JmtMeasure > jmt_parse_measures(const std::string &result_path, const std::string &command_output)
Port of getResultsJSIM: every <measure> of the result document.
const char * jmt_result_ext(const std::string &mode)
The suffix jmt.commandline.Jmt appends to the model path, per mode.
static const char * JMT_JAR_SHA256
The digest a fetched jar must have, and the reason the fetch can be trusted.
std::string jmt_method_refusal(const qn::NetworkStruct< T > &sn, const std::string &method, const JmtOptions &opt)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
PollingType
Polling service disciplines, with the values of MATLAB PollingType.
@ KLIMITED
serve at most K per visit (K in pollingPar)
@ EXHAUSTIVE
serve until the queue empties
std::string name(const Element *el, const std::string &key="name")
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.
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
FeatureSet jmt_feature_set(const std::string &method)
Port of SolverJMT.getFeatureSet (@@SolverJMT/SolverJMT.m:180-290).
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.
bool jmva_is_closed_only(const std::string &method)
True for the JMVA algorithms that solve a CLOSED product-form network only.
std::vector< double > stat(const Matrix< double > &A)
Stationary distribution of a stochastic matrix: the left eigenvector for eigenvalue 1,...
std::vector< std::pair< std::size_t, std::size_t > > fj_sorted_pairs(const S &sn)
The Fork/Join pairs of a struct, sortrows([forks joins]) over find(sn.fj): ascending in fork node,...
FjMetrics< T > fj_metrics_zero(const S &sn, std::size_t nclasses)
An empty-but-shaped result: every pair of sn, all three matrices zero.
int run_inherit(const std::vector< std::string > &argv)
Runs a command with the parent's stdout and stderr, e.g.
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
std::string trim(const std::string &s)
Trims ASCII whitespace from both ends, as Java's String.trim() does.
std::string sha256_file_hex(const std::string &path)
The digest of a file's contents, streamed.
std::unique_ptr< Element > parse_file(const std::string &path)
Read and parse a file.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
SHA-256 (FIPS 180-4), for verifying a file the port did not build.
Chain aggregation and de-aggregation.
Ports of the sn_has_* / sn_is_* predicate family of matlab/src/api/sn.
The DECLARED side of the gate: one feature set per solver.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
An HTTP response, with the body already de-chunked.
std::string body
Response body, decoded.
int status
HTTP status code.
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
One <measure> of a JMT result document, by its attributes.
The options of one JMT solve, SolverOptions('JMT') restricted to what is read.
int timeout
seconds, for the REST and subprocess arms
int replications
options.config.replications: the independent JSIM runs the transient ensemble of default averages ove...
std::string method
default | jsim | jmva | jmva.<alg>
std::string container
options.config.container, an image override
std::string rest_url
a JMT REST server; empty selects the local JVM
bool keep
keep the scratch directory after the solve
double confint
0 disables the confidence interval columns
double max_events
JSIM event budget, -1 unlimited; set by the samplers as MATLAB's maxEvents.
double max_simulated_time
double samples
samples per measure; raised to 5000 below
The result of a JMT solve: the shared AvgResult plus what only JMT reports.
Matrix< T > Tard
getResults.m result.Avg.Tard (stations x classes) and .SysTard (1 x classes).
Matrix< T > SysTardCI
their half-widths, empty when disabled
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 > MemOcc
FCR-only: weighted and memory occupation.
Matrix< T > ACI
confidence half-widths, empty when disabled
T log_norm_const
result.Prob.logNormConstAggr, the log normalizing constant JMVA reports.
Matrix< T > DropRateNfcr
(nregions x nclasses) carried and lost rate
static constexpr double FineTol
The metrics getAvg returns, after filtering.
The chain-level view of a layer, as sn_get_demands_chain returns it.
Matrix< T > ST
(M x K) class-level mean service time, 0 where disabled
std::vector< std::size_t > refstatchain
(C) 1-based reference station
Matrix< T > alpha
(M x K) class share of its chain's visits at a station
Matrix< T > STchain
(M x C) mean service time
Matrix< T > Vchain
(M x C) visits
static constexpr double FineTol
static constexpr double Zero
FINITE CAPACITY REGIONS, MATLAB's refreshRegions output.
std::vector< DropStrategy > rule
per class
std::vector< bool > members
membership, independent of the caps
self.result.FJ: one row per Fork/Join pair, one column per class.
std::vector< std::size_t > forks
1-based fork node of each pair
Matrix< T > Tput
FJTput, P x R.
Matrix< T > QLen
FJQLen, P x R.
std::size_t npairs() const
std::vector< std::size_t > joins
1-based join node of each pair
Matrix< T > RespT
FJRespT, P x R.
Outcome of a captured command.
int exitCode
Exit status, or -1 when the command could not run.
bool timedOut
True when the deadline expired and the child was killed.
std::string out
Everything the command wrote to stdout.
bool has_attr(const std::string &key) const
std::string attr(const std::string &key) const
Attribute value, or the empty string when absent (org.w3c.dom semantics).
Running an external command and capturing its output, with a deadline.
A scratch directory for the subprocess wrappers, the port's lineTempName.
A minimal XML DOM: read for the .lqnx interchange format, write for the JMT .jsimg and ....