5#ifndef LINE_SOLVERS_WRAPPERS_JMT_SOLVER_JMT_H
6#define LINE_SOLVERS_WRAPPERS_JMT_SOLVER_JMT_H
98static const char*
JMT_JAR_URL =
"https://line-solver.sourceforge.net/latest/JMT.jar";
116 "932cf6b34b04f9ba5b7c3ba0b07dc6c9fa80aa816ffcad7a39ac5d20a31bb9cd";
139inline bool is_file(
const std::string& p) {
141 return !p.empty() && ::stat(p.c_str(), &st) == 0 && S_ISREG(st.st_mode);
144inline bool is_exec(
const std::string& p) {
146 return !p.empty() && ::stat(p.c_str(), &st) == 0 && ::access(p.c_str(), X_OK) == 0;
149inline bool is_dir(
const std::string& p) {
151 return !p.empty() && ::stat(p.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
154inline std::string env_or_empty(
const char* name) {
155 const char* v = std::getenv(name);
156 return v !=
nullptr ? std::string(v) : std::string();
159inline std::string exe_dir() {
161 const ssize_t n = ::readlink(
"/proc/self/exe", buf,
sizeof(buf) - 1);
162 if (n <= 0)
return std::string();
163 std::string p(buf,
static_cast<std::size_t
>(n));
164 const std::size_t s = p.find_last_of(
'/');
165 return s == std::string::npos ? std::string() : p.substr(0, s);
168inline std::string cwd() {
170 return ::getcwd(buf,
sizeof(buf)) !=
nullptr ? std::string(buf) : std::string();
190inline std::string private_tmpdir_flag(
const std::string& model_path) {
191 const std::size_t s = model_path.find_last_of(
'/');
192 const std::string dir = s == std::string::npos ? cwd() : model_path.substr(0, s);
193 return "-Djava.io.tmpdir=" + (dir.empty() ? cwd() : dir);
208inline std::string jmt_jar_path() {
209 const std::string forced = env_or_empty(
"LINE_JMT_DIR");
210 if (!forced.empty())
return is_file(forced +
"/JMT.jar") ? forced : std::string();
211 const std::string roots[2] = {exe_dir(), cwd()};
212 for (
int r = 0; r < 2; ++r) {
213 if (roots[r].empty())
continue;
214 std::string dir = roots[r];
215 for (
int k = 0; k < 7; ++k) {
216 if (is_file(dir +
"/JMT.jar"))
return dir;
217 if (is_file(dir +
"/common/JMT.jar"))
return dir +
"/common";
221 return std::string();
225inline std::string which_on_path(
const std::string& name) {
226 const std::string path = env_or_empty(
"PATH");
228 while (b <= path.size()) {
229 const std::size_t e = path.find(
':', b);
230 const std::string dir =
231 path.substr(b, e == std::string::npos ? std::string::npos : e - b);
232 if (!dir.empty() && is_exec(dir +
"/" + name))
return dir +
"/" + name;
233 if (e == std::string::npos)
break;
236 return std::string();
240inline std::string find_java() {
241 const std::string forced = env_or_empty(
"LINE_JAVA");
242 if (!forced.empty())
return is_exec(forced) ? forced : std::string();
243 const std::string home = env_or_empty(
"JAVA_HOME");
244 if (!home.empty() && is_exec(home +
"/bin/java"))
return home +
"/bin/java";
245 return which_on_path(
"java");
249inline bool docker_consented() {
250 const std::string v = env_or_empty(
"LINE_JMT_DOCKER");
251 return v ==
"1" || v ==
"true" || v ==
"TRUE" || v ==
"yes" || v ==
"y" || v ==
"Y";
263inline bool download_consented() {
264 const std::string v = env_or_empty(
"LINE_JMT_DOWNLOAD");
265 return v ==
"1" || v ==
"true" || v ==
"TRUE" || v ==
"yes" || v ==
"y" || v ==
"Y";
278inline std::string jmt_download_dir() {
279 const std::string forced = env_or_empty(
"LINE_JMT_DIR");
280 if (!forced.empty())
return forced;
281 const std::string roots[2] = {exe_dir(), cwd()};
282 for (
int r = 0; r < 2; ++r) {
283 if (roots[r].empty())
continue;
284 std::string dir = roots[r];
285 for (
int k = 0; k < 7; ++k) {
286 if (is_dir(dir +
"/common"))
return dir +
"/common";
309inline std::string find_fetcher() {
310 const std::string curl = which_on_path(
"curl");
311 return curl.empty() ? which_on_path(
"wget") : curl;
314inline bool fetch_url(
const std::string& tool,
const std::string& url,
const std::string& dest) {
315 const bool is_curl = tool.size() >= 4 && tool.compare(tool.size() - 4, 4,
"curl") == 0;
317 std::vector<std::string> argv;
318 argv.push_back(tool);
319 argv.push_back(
"-fL");
320 argv.push_back(
"--proto");
321 argv.push_back(
"=https");
322 argv.push_back(
"--proto-redir");
323 argv.push_back(
"=https");
324 argv.push_back(
"--tlsv1.2");
325 argv.push_back(
"--connect-timeout");
326 argv.push_back(
"30");
327 argv.push_back(
"--max-time");
328 argv.push_back(
"1800");
329 argv.push_back(
"-o");
330 argv.push_back(dest);
334 if (tool.empty())
return false;
335 std::vector<std::string> argv;
336 argv.push_back(tool);
337 argv.push_back(
"--https-only");
338 argv.push_back(
"--timeout=30");
339 argv.push_back(
"--tries=2");
340 argv.push_back(
"-O");
341 argv.push_back(dest);
347inline std::string expected_jar_sha256() {
348 std::string want =
util::trim(env_or_empty(
"LINE_JMT_SHA256"));
350 for (std::size_t i = 0; i < want.size(); ++i)
351 want[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(want[i])));
371inline std::string jmt_download_jar() {
372 if (!download_consented())
return std::string();
374 std::string url = env_or_empty(
"LINE_JMT_URL");
376 if (url.compare(0, 8,
"https://") != 0)
377 throw InputError(
"SolverJMT: refusing to fetch JMT.jar over '" + url +
378 "': the download must be https, so that the transfer is authenticated "
379 "before the digest authenticates the file");
381 const std::string dir = jmt_download_dir();
382 if (!is_dir(dir) || ::access(dir.c_str(), W_OK) != 0)
383 throw InputError(
"SolverJMT: cannot write JMT.jar to '" + dir +
384 "': the directory does not exist or is not writable. Set LINE_JMT_DIR to "
385 "a directory that is");
387 const std::string tool = find_fetcher();
389 throw InputError(
"SolverJMT: LINE_JMT_DOWNLOAD is set but neither curl nor wget is on "
390 "PATH, and this port has no TLS client of its own. Install one, or place "
394 const std::string dest = dir +
"/JMT.jar";
395 const std::string part = dest +
".part-" + std::to_string(
static_cast<long>(::getpid()));
396 ::unlink(part.c_str());
397 std::printf(
"\nJava Modelling Tools cannot be found. Fetching %s (about 31 MiB) into %s\n",
398 url.c_str(), dir.c_str());
400 if (!fetch_url(tool, url, part)) {
401 ::unlink(part.c_str());
402 throw InputError(
"SolverJMT: the download of " + url +
" failed; see the transfer's own "
406 const std::string want = expected_jar_sha256();
410 const long long bytes = ::stat(part.c_str(), &st) == 0 ?
static_cast<long long>(st.st_size)
412 ::unlink(part.c_str());
414 "SolverJMT: the jar fetched from " + url +
" is not the one this port pins.\n" +
415 " expected sha256 " + want +
"\n" +
" received sha256 " +
416 (got.empty() ? std::string(
"<unreadable>") : got) +
" (" + std::to_string(bytes) +
418 "The file has been discarded. That URL serves a rolling 'latest' build, so an upstream "
419 "republication looks exactly like this: check the digest against the publisher and, if "
420 "it is the build you want, set LINE_JMT_SHA256 to it");
423 if (::rename(part.c_str(), dest.c_str()) != 0) {
424 ::unlink(part.c_str());
425 throw InputError(
"SolverJMT: could not move the verified jar into place at '" + dest +
"'");
427 std::printf(
"Verified sha256 %s and installed %s\n", got.c_str(), dest.c_str());
432inline std::string read_file(
const std::string& path) {
433 std::ifstream f(path.c_str(), std::ios::binary);
434 if (!f)
throw InputError(
"SolverJMT: cannot read '" + path +
"'");
435 std::ostringstream ss;
440inline void write_file(
const std::string& path,
const std::string& text) {
441 std::ofstream f(path.c_str(), std::ios::binary);
442 if (!f)
throw InputError(
"SolverJMT: cannot write '" + path +
"'");
443 f.write(text.data(),
static_cast<std::streamsize
>(text.size()));
444 if (!f)
throw InputError(
"SolverJMT: write failed on '" + path +
"'");
448inline std::string json_escape(
const std::string& s) {
450 out.reserve(s.size() + 16);
451 for (std::size_t i = 0; i < s.size(); ++i) {
452 const unsigned char c =
static_cast<unsigned char>(s[i]);
454 case '"': out +=
"\\\"";
break;
455 case '\\': out +=
"\\\\";
break;
456 case '\n': out +=
"\\n";
break;
457 case '\r': out +=
"\\r";
break;
458 case '\t': out +=
"\\t";
break;
462 std::snprintf(buf,
sizeof(buf),
"\\u%04x", c);
481inline std::string json_string_field(
const std::string& body,
const std::string& key) {
482 const std::string needle =
"\"" + key +
"\"";
483 std::size_t p = body.find(needle);
484 if (p == std::string::npos)
return std::string();
485 p = body.find(
':', p + needle.size());
486 if (p == std::string::npos)
return std::string();
488 while (p < body.size() && (body[p] ==
' ' || body[p] ==
'\t' || body[p] ==
'\n')) ++p;
489 if (p >= body.size() || body[p] !=
'"')
return std::string();
492 while (p < body.size() && body[p] !=
'"') {
493 if (body[p] ==
'\\' && p + 1 < body.size()) {
496 case 'n': out.push_back(
'\n');
break;
497 case 'r': out.push_back(
'\r');
break;
498 case 't': out.push_back(
'\t');
break;
500 if (p + 4 < body.size()) {
501 const long cp = std::strtol(body.substr(p + 1, 4).c_str(),
nullptr, 16);
502 if (cp < 0x80) out.push_back(
static_cast<char>(cp));
507 default: out.push_back(body[p]);
510 out.push_back(body[p]);
522inline util::ProcResult
jmt_run_docker(
const std::string& image,
const std::string& mode,
523 const std::string& model_path,
long seed,
528 if (mode ==
"sim")
return "jsim";
529 if (mode ==
"mva")
return "jmva";
530 throw InputError(
"SolverJMT: unknown JMT analysis mode '" + mode +
"'");
535 return !detail::jmt_jar_path().empty() && !detail::find_java().empty();
543 const std::string& model_path,
long seed,
545 std::string url = rest_url;
546 while (!url.empty() && url[url.size() - 1] ==
'/') url.erase(url.size() - 1);
548 if (url.size() < 8 || url.substr(url.size() - 8) !=
"/solve/" + mode.substr(0, 1))
549 if (url.find(
"/api/v") == std::string::npos) url +=
"/api/v1/solve/" + mode;
551 std::string body =
"{\"model\":{\"content\":\"" +
552 detail::json_escape(detail::read_file(model_path)) +
553 "\",\"base64\":false}";
557 if (mode ==
"sim") body +=
",\"options\":{\"seed\":" + std::to_string(seed) +
"}";
562 throw InputError(
"SolverJMT: the JMT REST server at " + url +
" answered HTTP " +
563 std::to_string(resp.
status));
564 const std::string status = detail::json_string_field(resp.
body,
"status");
565 if (status !=
"completed") {
566 const std::string err = detail::json_string_field(resp.
body,
"error");
567 throw InputError(
"SolverJMT: the JMT REST solve failed: " +
568 (err.empty() ? std::string(
"unspecified error") : err));
570 const std::string
xml = detail::json_string_field(resp.
body,
"result_xml");
573 "SolverJMT: the JMT REST response carries no result document. The server was asked "
574 "to include the raw output; check that include_raw_output is not disabled");
578 r.
out = detail::json_string_field(resp.
body,
"stdout");
593 std::string jar_dir = detail::jmt_jar_path();
594 const std::string java = detail::find_java();
599 if (jar_dir.empty() && !java.empty()) jar_dir = detail::jmt_download_jar();
600 if (!jar_dir.empty() && !java.empty()) {
601 std::vector<std::string> argv;
602 argv.push_back(java);
603 argv.push_back(detail::private_tmpdir_flag(model_path));
604 argv.push_back(
"-cp");
605 argv.push_back(jar_dir +
"/JMT.jar");
606 argv.push_back(
"jmt.commandline.Jmt");
607 argv.push_back(mode);
608 argv.push_back(model_path);
609 argv.push_back(
"-seed");
610 argv.push_back(std::to_string(seed));
611 argv.push_back(
"--illegal-access=permit");
619 throw NumericError(
"SolverJMT: the JMT run did not finish within " +
620 std::to_string(
opt.timeout) +
"s and was killed");
626 std::string image =
opt.container.empty() ? detail::env_or_empty(
"LINE_JMT_IMAGE")
637 "SolverJMT: no way to reach JMT. Install a Java runtime and put JMT.jar in common/ "
639 "), or set LINE_JMT_DOWNLOAD=1 to let curl or wget fetch and verify that jar for you, or "
640 "point options.rest_url at a JMT REST server, or set LINE_JMT_DOCKER=1 to allow the " +
641 image +
" image to be pulled and used");
655 const std::string& model_path,
long seed,
658 const std::size_t slash = model_path.find_last_of(
'/');
659 const std::string base =
660 slash == std::string::npos ? model_path : model_path.substr(slash + 1);
661 const std::string staged = work.
path() +
"/" + base;
662 detail::write_file(staged, detail::read_file(model_path));
664 const std::string uid = std::to_string(
static_cast<long>(::getuid()));
665 const std::string gid = std::to_string(
static_cast<long>(::getgid()));
666 std::vector<std::string> argv;
667 argv.push_back(
"docker");
668 argv.push_back(
"run");
669 argv.push_back(
"--rm");
670 argv.push_back(
"--user");
671 argv.push_back(uid +
":" + gid);
672 argv.push_back(
"-v");
673 argv.push_back(work.
path() +
":" + work.
path());
674 argv.push_back(
"-w");
675 argv.push_back(work.
path());
676 argv.push_back(image);
677 argv.push_back(mode);
678 argv.push_back(base);
679 argv.push_back(
"-seed");
680 argv.push_back(std::to_string(seed));
683 throw NumericError(
"SolverJMT: the JMT container run did not finish within " +
684 std::to_string(
opt.timeout) +
"s and was killed");
686 const std::string produced = staged +
"-result." +
jmt_result_ext(mode);
687 if (detail::is_file(produced))
688 detail::write_file(model_path +
"-result." +
jmt_result_ext(mode),
689 detail::read_file(produced));
710 const std::string& command_output) {
711 if (!detail::is_file(result_path)) {
713 "SolverJMT: JMT did not output a result file, the simulation has likely failed.";
714 if (!command_output.empty()) msg +=
" JMT output: " + command_output;
717 const std::unique_ptr<xml::Element> doc =
xml::parse_file(result_path);
718 std::vector<JmtMeasure> out;
719 const std::vector<const xml::Element*> ms = doc->by_tag(
"measure");
720 for (std::size_t i = 0; i < ms.size(); ++i) {
727 m.
mean = std::strtod(e->
attr(
"meanValue").c_str(),
nullptr);
731 m.
lower = std::strtod(e->
attr(
"lowerLimit").c_str(),
nullptr);
732 m.
upper = std::strtod(e->
attr(
"upperLimit").c_str(),
nullptr);
777 const std::vector<JmtMeasure>& measures,
bool confint) {
778 const std::size_t M =
sn.nstations, K =
sn.nclasses, F =
sn.regions.size();
780 const double nan = std::numeric_limits<double>::quiet_NaN();
798 for (std::size_t f = 0; f < F; ++f)
799 for (std::size_t r = 0; r < K; ++r) {
804 std::map<std::string, std::size_t> station_of, class_of;
805 for (std::size_t i = 1; i <=
sn.nodes.size(); ++i) station_of[
sn.nodes[i - 1].name] = i;
806 for (std::size_t r = 1; r <= K; ++r) class_of[
sn.classes[r - 1].name] = r;
809 std::vector<double> chainpop(K, 0.0);
810 for (std::size_t c = 0; c <
sn.inchain.size(); ++c) {
812 for (std::size_t r :
sn.inchain[c]) {
813 const double n =
sn.classes[r - 1].population;
814 if (std::isfinite(n)) tot += n;
816 for (std::size_t r :
sn.inchain[c]) chainpop[r - 1] = tot;
819 for (std::size_t i = 0; i < measures.size(); ++i) {
827 if (is_fcr && m.
station.compare(0, 8,
"FCRegion") == 0) {
828 const long f = std::strtol(m.
station.substr(8).c_str(),
nullptr, 10);
829 if (f < 1 ||
static_cast<std::size_t
>(f) > F)
continue;
830 const std::size_t row = M +
static_cast<std::size_t
>(f) - 1;
836 const double per_class = m.
mean /
static_cast<double>(K);
837 for (std::size_t r = 0; r < K; ++r) {
857 const auto ni = station_of.find(m.
station);
858 const auto ci = class_of.find(m.
job_class);
859 if (ni == station_of.end() || ci == class_of.end())
continue;
861 const auto cp =
sn.nodeparam.find(ni->second);
862 if (cp ==
sn.nodeparam.end())
continue;
864 if (hit.empty()) hit.assign(K, zero);
867 for (std::size_t r = 0; r < cp->second.hitclass.size(); ++r)
868 if (cp->second.hitclass[r] == ci->second)
873 const auto ni = station_of.find(m.
station);
874 const auto ci = class_of.find(m.
job_class);
875 if (ni == station_of.end() || ci == class_of.end())
continue;
876 const std::size_t ist =
sn.nodes[ni->second - 1].station;
877 if (ist == 0)
continue;
878 const std::size_t r = ci->second;
879 const bool open = !std::isfinite(
sn.classes[r - 1].population);
885 res.
avg.QN(ist - 1, r - 1) = v;
886 if (confint) res.
QCI(ist - 1, r - 1) = ci_v;
888 res.
avg.UN(ist - 1, r - 1) = v;
889 if (confint) res.
UCI(ist - 1, r - 1) = ci_v;
891 res.
avg.RN(ist - 1, r - 1) = v;
892 if (confint) res.
RCI(ist - 1, r - 1) = ci_v;
894 res.
avg.TN(ist - 1, r - 1) = v;
895 if (confint) res.
TCI(ist - 1, r - 1) = ci_v;
897 res.
avg.AN(ist - 1, r - 1) = v;
898 if (confint) res.
ACI(ist - 1, r - 1) = ci_v;
918 const std::size_t M =
sn.nstations, K =
sn.nclasses, F =
sn.regions.size();
923 for (std::size_t f = 0; f < F; ++f)
924 for (std::size_t r = 0; r < K; ++r) res.
TNfcr(f, r) = res.
avg.TN(M + f, r);
926 for (std::size_t f = 0; f < F; ++f) {
928 for (std::size_t r = 1; r <= K; ++r) {
930 for (std::size_t ii = 1; ii <= M; ++ii) {
932 for (std::size_t j = 1; j <= M; ++j) {
934 for (std::size_t rp = 1; rp <= K; ++rp) {
935 const std::size_t a = (j - 1) * K + (rp - 1);
936 const std::size_t b = (ii - 1) * K + (r - 1);
937 if (
sn.rt.rows() <= a ||
sn.rt.cols() <= b)
continue;
974 const std::string& command_output) {
975 if (!detail::is_file(result_path)) {
977 "SolverJMT: JMT did not output a result file, the analysis has likely failed.";
978 if (!command_output.empty()) msg +=
" JMT output: " + command_output;
981 const std::size_t M =
sn.nstations, K =
sn.nclasses, C =
sn.nchains;
993 std::map<std::string, std::size_t> station_of;
994 for (std::size_t i = 1; i <= M; ++i)
995 station_of[
sn.nodes[
sn.station_to_node[i - 1] - 1].name] = i;
997 const std::unique_ptr<xml::Element> doc =
xml::parse_file(result_path);
999 const std::vector<const xml::Element*>
nc = doc->by_tag(
"normconst");
1002 std::numeric_limits<double>::quiet_NaN())
1004 std::strtod(
nc[0]->attr(
"logValue").c_str(),
nullptr));
1008 if (
sn.sourceIdx != 0)
1009 for (std::size_t r = 0; r < K; ++r)
1010 if (!
sn.disabled[
sn.sourceIdx - 1][r])
1011 res.
avg.TN(
sn.sourceIdx - 1, r) =
sn.rates(
sn.sourceIdx - 1, r);
1013 const std::vector<const xml::Element*> blocks = doc->by_tag(
"stationresults");
1014 for (std::size_t b = 0; b < blocks.size(); ++b) {
1015 const auto si = station_of.find(blocks[b]->attr(
"station"));
1016 if (si == station_of.end())
continue;
1017 const std::size_t i = si->second;
1021 double ns =
sn.stations[i - 1].nservers;
1022 for (
const T& s :
sn.stations[i - 1].lldscaling)
1024 const std::vector<const xml::Element*> crs = blocks[b]->by_tag(
"classresults");
1025 for (std::size_t c = 1; c <= crs.size() && c <= C; ++c) {
1026 const std::vector<const xml::Element*> ms = crs[c - 1]->by_tag(
"measure");
1033 double chain_tput = std::numeric_limits<double>::quiet_NaN();
1034 for (std::size_t m = 0; m < ms.size(); ++m) {
1035 if (ms[m]->attr(
"measureType") ==
"Throughput") {
1036 const std::string traw = ms[m]->attr(
"meanValue");
1037 chain_tput = traw ==
"NaN" ? std::numeric_limits<double>::quiet_NaN()
1038 : std::strtod(traw.c_str(),
nullptr);
1042 for (std::size_t m = 0; m < ms.size(); ++m) {
1043 const std::string kind = ms[m]->attr(
"measureType");
1044 const std::string raw = ms[m]->attr(
"meanValue");
1045 const double val = raw ==
"NaN" ? std::numeric_limits<double>::quiet_NaN()
1046 : std::strtod(raw.c_str(),
nullptr);
1048 const double vchain_ref =
1051 for (std::size_t k :
sn.inchain[c - 1]) {
1055 if (kind ==
"Utilization") {
1056 if (vchain_ref == 0.0)
continue;
1057 v = stk * chain_tput / vchain_ref * al;
1058 if (std::isfinite(ns)) v /= ns;
1060 }
else if (kind ==
"Throughput") {
1062 }
else if (kind ==
"Number of Customers") {
1063 if (stchain == 0.0 || vchain_ref == 0.0)
continue;
1064 res.
avg.QN(i - 1, k - 1) =
1066 }
else if (kind ==
"Residence time" || kind ==
"Residence Time") {
1067 if (stchain == 0.0 || vchain_ref == 0.0)
continue;
1068 double visits = 0.0;
1069 if (c - 1 <
sn.visits.size()) {
1070 const std::size_t sfi =
sn.stateful_of_station(i);
1071 if (sfi != 0 &&
sn.visits[c - 1].rows() >= sfi)
1074 if (visits == 0.0)
continue;
1075 v = (val / visits) * stk / stchain / vchain_ref * al;
1097 return {
"default",
"jsim",
"jmva",
"jmva.amva",
"jmva.mva",
1098 "jmva.recal",
"jmva.comom",
"jmva.chow",
"jmva.bs",
"jmva.aql",
1099 "jmva.lin",
"jmva.dmlin"};
1109 if (method ==
"replication")
1110 return "SolverJMT: the 'replication' method was removed: use a finite timespan with "
1111 "options.config.replications";
1112 return std::string();
1144 if (!removed.empty())
return removed;
1152 if (
sn.has_immediate_feedback())
1153 return "SolverJMT: JMT has no immediate-feedback semantics (a job re-entering service "
1154 "while HOLDING the server); use SolverCTMC, SolverSSA or SolverLDES";
1156 double maxFinite = 0.0;
1157 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1158 const double c =
static_cast<double>(
sn.stations[i].nservers);
1159 if (std::isfinite(c)) maxFinite = std::max(maxFinite, c);
1161 if (maxFinite > 1.0)
return "SolverJMT: " + method +
" does not support multi-server stations";
1163 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1164 const std::vector<T>& alpha =
sn.stations[i].lldscaling;
1165 if (alpha.empty())
continue;
1168 if (c == 1.0)
continue;
1169 bool ok = (c >= 1.0) && (c == std::floor(c));
1170 for (std::size_t n = 0; ok && n < alpha.size(); ++n)
1174 return "SolverJMT: station '" +
sn.stations[i].name +
1175 "' uses a load-dependent scaling that is not the multiserver encoding alpha(n) "
1176 "= min(n,c): JMT has no representation for it, since both the JSIM and the JMVA "
1177 "writer carry the scaling as a server count, and the model would be solved at "
1178 "the nominal service rate. Use SolverCTMC, SolverNC, SolverMVA or SolverSSA, "
1179 "which read sn.lldscaling directly";
1189 const bool is_jmva = method.compare(0, 4,
"jmva") == 0;
1190 const bool is_jsim = method ==
"default" || method ==
"jsim";
1191 if (is_jmva || is_jsim) {
1193 if (!cap_reason.empty())
return cap_reason;
1195 return std::string();
1209 if (
opt.samples < 5000.0)
opt.samples = 5000.0;
1215 if (std::find(valid.begin(), valid.end(),
opt.method) == valid.end())
1226 const bool is_mva =
opt.method.compare(0, 4,
"jmva") == 0;
1227 const std::string mode = is_mva ?
"mva" :
"sim";
1230 const std::string model_path = work.
path() +
"/model." + (is_mva ?
"jmva" :
"jsim");
1252 const std::string result_path = model_path +
"-result." +
jmt_result_ext(mode);
1265 for (std::size_t i = 0; i <
sn.nstations; ++i)
1266 for (std::size_t r = 0; r <
sn.nclasses; ++r) res.
avg.WN(i, r) = WNst(i, r);
1277 for (std::size_t i = 0; i <
sn.nstations; ++i) {
1278 if (
sn.stations[i].nodetype != qn::NodeType::Source)
continue;
1279 for (std::size_t r = 0; r <
sn.nclasses; ++r)
1293 for (std::size_t i = 0; i <
sn.nstations; ++i)
1294 for (std::size_t r = 0; r <
sn.nclasses; ++r)
1302 res.
avg.actualmethod =
opt.method;
NumericError(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").
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 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).
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.
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)
The structural half of SolverJMT's method gate; empty when admissible.
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.
bool jmva_is_closed_only(const std::string &method)
Port of SolverJMT.getFeatureSet (@@SolverJMT/SolverJMT.m:180-290).
std::vector< double > stat(const Matrix< double > &A)
Stationary distribution of a stochastic matrix: the left eigenvector for eigenvalue 1,...
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.
The DECLARED side of the gate: one feature set per solver.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
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_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.
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 Zero
FINITE CAPACITY REGIONS, MATLAB's refreshRegions output.
std::vector< DropStrategy > rule
per class
std::vector< bool > members
membership, independent of the caps
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 ....