5#ifndef LINE_SOLVERS_WRAPPERS_LQNS_LQNS_QNSOLVER_H
6#define LINE_SOLVERS_WRAPPERS_LQNS_LQNS_QNSOLVER_H
98 return {
"default",
"qns",
"qns.conway",
"qns.rolia",
99 "qns.zhou",
"qns.suri",
"qns.reiser",
"qns.schmidt"};
104 std::string m = method;
105 std::transform(m.begin(), m.end(), m.begin(),
106 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
107 return m ==
"qns" || m.compare(0, 4,
"qns.") == 0;
116 std::string m = method;
117 std::transform(m.begin(), m.end(), m.begin(),
118 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
119 return m.compare(0, 4,
"qns.") == 0 ? m.substr(4) : std::string(
"default");
124 return m ==
"conway" || m ==
"reiser" || m ==
"rolia" || m ==
"zhou";
144 return "SolverLQNS does not support immediate feedback (sn.immfeed): neither the JMVA "
145 "document qnsolver reads nor the LQN QN2LQN writes can keep a self-looping job on "
146 "its server. Use SolverCTMC or SolverSSA, whose state space carries the self-loop.";
150 bool has_multiserver =
false;
151 for (std::size_t i = 0; i < L.
nstations && !has_multiserver; ++i) {
152 const double c = L.
stations[i].nservers;
153 if (std::isfinite(c) && c > 1.0) has_multiserver =
true;
155 if (!has_multiserver)
return std::string();
158 return "SolverLQNS: the multiserver approximation '" + ms +
159 "' is one LQNS offers and qnsolver does not: 'qnsolver -m' accepts conway, reiser, "
160 "rolia and zhou only; suri and schmidt are available only on the non-product-form "
161 "closed SolverLQNS branch.";
176inline std::vector<std::string> qnsolver_argv() {
177 return {
"env",
"-u",
"LD_LIBRARY_PATH",
"qnsolver"};
190 std::vector<std::string> argv = detail::qnsolver_argv();
191 argv.push_back(
"--help");
198 const std::vector<std::string> valid =
qns_methods();
199 if (std::find(valid.begin(), valid.end(), method) == valid.end())
200 throw InputError(
"SolverLQNS: unknown method '" + method +
201 "' on a Network; valid methods are default, qns, qns.conway, qns.rolia, "
202 "qns.zhou, qns.suri, qns.reiser, qns.schmidt");
224 if (bare !=
"default")
return bare;
225 if (!
opt.multiserver.empty())
return opt.multiserver;
234 std::vector<double> Q, W, U, Tp;
237inline double parse_double_or_zero(
const std::string& s) {
238 if (s.empty())
return 0.0;
240 const double v = std::strtod(s.c_str(), &end);
241 if (end == s.c_str())
return 0.0;
259inline bool parse_row(
const std::string& line, std::size_t nchains, ParsedRow* out) {
260 if (line.find(
',') == std::string::npos)
return false;
261 if (line.find(
'$') != std::string::npos)
return false;
263 for (std::size_t i = 0; i < line.size(); ++i)
264 if (line[i] !=
' ' && line[i] !=
'\t' && line[i] !=
'\r') s += line[i];
266 std::vector<std::string> parts;
269 const std::size_t j = s.find(
',', b);
270 parts.push_back(s.substr(b, j == std::string::npos ? j : j - b));
271 if (j == std::string::npos)
break;
274 const std::size_t stride = nchains == 1 ? 1 : nchains + 1;
275 if (parts.size() < 1 + 4 * stride)
return false;
277 out->station = parts[0];
278 out->Q.assign(nchains, 0.0);
279 out->W.assign(nchains, 0.0);
280 out->U.assign(nchains, 0.0);
281 out->Tp.assign(nchains, 0.0);
283 for (std::size_t c = 0; c < nchains; ++c) out->Q[c] = parse_double_or_zero(parts[ptr + c]);
285 for (std::size_t c = 0; c < nchains; ++c) out->W[c] = parse_double_or_zero(parts[ptr + c]);
287 for (std::size_t c = 0; c < nchains; ++c) out->U[c] = parse_double_or_zero(parts[ptr + c]);
289 for (std::size_t c = 0; c < nchains; ++c) out->Tp[c] = parse_double_or_zero(parts[ptr + c]);
306 for (std::size_t i = 0; i < L.
nstations; ++i) {
308 if (nt == qn::NodeType::Queue || nt == qn::NodeType::Delay ||
309 nt == qn::NodeType::Source)
313 "' is not a Queue, a Delay or a Source, and the JMVA document qnsolver reads "
314 "carries no other station type");
318 "SolverLQNS: class priorities are outside the product-form envelope qnsolver "
356 "SolverLQNS does not support immediate feedback (sn.immfeed): neither the JMVA "
357 "document qnsolver reads nor the LQN QN2LQN writes can keep a self-looping job on "
358 "its server. Use SolverCTMC or SolverSSA, whose state space carries the self-loop.");
362 bool has_open =
false;
363 for (std::size_t k = 0; k < K; ++k)
364 if (!std::isfinite(L.
classes[k].population)) has_open =
true;
369 "SolverLQNS: class priorities are outside the QN2LQN conversion envelope");
374 lo.samples =
static_cast<double>(
opt.samples);
376 lo.timeout_seconds =
opt.timeout;
377 SolverLQNS<T> solver(layered, lo);
378 const LqnsSolution<T> ls = solver.get_ensemble_avg();
381 Matrix<T> Q(M, K, zero), U(M, K, zero), R(M, K, zero), Tp(M, K, zero);
383 const auto element = [&](
const std::string& name,
lqn::LqnElement kind) {
384 for (std::size_t idx = 1; idx <=
lsn.nidx; ++idx)
385 if (
lsn.type[idx] == kind &&
lsn.names[idx] == name)
return idx;
386 return std::size_t(0);
388 for (std::size_t i = 0; i < M; ++i) {
391 if (type != qn::NodeType::Queue && type != qn::NodeType::Delay)
continue;
392 for (std::size_t r = 0; r < K; ++r) {
393 const std::string suffix = std::to_string(node + 1) +
"_" +
394 std::to_string(r + 1);
395 const std::size_t a = element(
"Q" + suffix, lqn::LqnElement::ACTIVITY);
396 if (a == 0)
continue;
397 if (ls.defined_Q[a]) Q(i, r) = ls.QN[a];
398 if (ls.defined_U[a]) U(i, r) = ls.UN[a];
399 if (ls.defined_R[a]) R(i, r) = ls.RN[a];
400 if (ls.defined_T[a]) Tp(i, r) = ls.TN[a];
406 const std::size_t e = element(
"E" + suffix, lqn::LqnElement::ENTRY);
407 if (e != 0 && !ls.defined_U[a] && ls.defined_U[e]) U(i, r) = ls.UN[e];
408 if (e != 0 && !ls.defined_T[a] && ls.defined_T[e]) Tp(i, r) = ls.TN[e];
410 const double servers = L.
stations[i].nservers;
411 if (std::isfinite(servers) && servers > 0.0)
412 U(i, r) = T(U(i, r) / num_traits<T>::from_double(servers));
416 mva::AvgResult<T> out;
429 out.method = opt.method;
431 out.actualmethod = opt.method ==
"default" ? (
"default/" + actual) : actual;
432 out.iter = ls.iterations;
446 bool has_multiserver =
false;
447 for (std::size_t i = 0; i < M; ++i) {
448 const double c = L.
stations[i].nservers;
449 if (std::isfinite(c) && c > 1.0) has_multiserver =
true;
460 "SolverLQNS: the multiserver approximation '" + ms +
461 "' is one LQNS offers and qnsolver does not: 'qnsolver -m' accepts conway, reiser, "
462 "rolia and zhou only; suri and schmidt are available only on the non-product-form "
463 "closed SolverLQNS branch");
467 "SolverLQNS needs the external 'qnsolver' binary on the PATH. It ships with LQNS "
468 "(http://www.sce.carleton.ca/rads/lqns/); LINE distributes no copy and runs none "
469 "from a container image, because that licence forbids redistribution");
471 util::TempDir tmp(
"qns");
472 if (opt.keep) tmp.keep();
473 const std::string model_file = tmp.file(
"model.jmva");
474 const std::string result_file = tmp.file(
"result.jmva");
478 std::vector<std::string> argv = detail::qnsolver_argv();
487 argv.push_back(
"-l");
488 argv.push_back(model_file);
489 if (has_multiserver) argv.push_back(
"-m" + ms);
490 argv.push_back(
"-o");
491 argv.push_back(result_file);
494 const util::ProcResult pr =
util::capture(argv, opt.timeout);
496 throw NumericError(
"SolverLQNS: qnsolver did not finish within " +
497 std::to_string(opt.timeout) +
"s and was killed");
498 if (pr.exitCode != 0)
499 throw NumericError(
"SolverLQNS: qnsolver exited with code " +
500 std::to_string(pr.exitCode) +
501 (pr.out.empty() ? std::string() : (
"\n" + pr.out)));
505 const T zero = num_traits<T>::from_int(0);
506 Matrix<T> Qchain(M, C, zero), Uchain(M, C, zero), Wchain(M, C, zero), Tchain(M, C, zero);
507 std::ifstream in(result_file.c_str());
512 throw NumericError(
"SolverLQNS: qnsolver wrote no result file at '" + result_file +
"'" +
513 (pr.out.empty() ? std::string() : (
"\nqnsolver said: " + pr.out)));
515 std::size_t nrows = 0;
516 while (std::getline(in, line)) {
517 detail::ParsedRow row;
518 if (!detail::parse_row(line, C, &row))
continue;
520 for (std::size_t i = 0; i < M; ++i)
525 if (idx == M)
continue;
526 for (std::size_t c = 0; c < C; ++c) {
527 Qchain(idx, c) = num_traits<T>::from_double(row.Q[c]);
528 Wchain(idx, c) = num_traits<T>::from_double(row.W[c]);
529 Uchain(idx, c) = num_traits<T>::from_double(row.U[c]);
530 Tchain(idx, c) = num_traits<T>::from_double(row.Tp[c]);
536 "SolverLQNS: qnsolver produced no station rows this model recognises; its output "
537 "names no station of the model");
546 std::vector<T> Xchain(C, zero);
547 for (std::size_t c = 0; c < C; ++c) {
549 if (Tchain(rstat - 1, c) > zero) {
550 Xchain[c] = Tchain(rstat - 1, c);
556 for (std::size_t i = 0; i < M; ++i)
557 if (d.Vchain(i, c) > zero && Tchain(i, c) > zero) {
558 Xchain[c] = T(Tchain(i, c) / d.Vchain(i, c));
565 Matrix<T> Rchain = Wchain;
566 for (std::size_t i = 0; i < M; ++i)
567 for (std::size_t c = 0; c < C; ++c)
568 if (std::isnan(num_traits<T>::to_double(Rchain(i, c)))) Rchain(i, c) = zero;
573 for (std::size_t i = 0; i < M; ++i) {
574 const double c = L.
stations[i].nservers;
575 if (!std::isfinite(c))
continue;
576 for (std::size_t j = 0; j < C; ++j)
577 Uchain(i, j) = T(Uchain(i, j) / num_traits<T>::from_double(c));
580 const mva::ClassResults<T> cr =
583 mva::AvgResult<T> out;
588 std::vector<std::vector<bool>> srcmask(M, std::vector<bool>(K,
false));
589 for (std::size_t i = 0; i < M; ++i)
590 if (L.
stations[i].nodetype == qn::NodeType::Source)
591 for (std::size_t k = 0; k < K; ++k) srcmask[i][k] =
true;
601 out.method = opt.method;
605 const std::string ran = has_multiserver ? ms : std::string(
"default");
606 out.actualmethod = opt.method ==
"default" ? (
"default/" + ran) : ran;
633 if (label.compare(0, 8,
"default/") == 0) {
635 label = label.substr(prefix.size());
637 if (label !=
"default") label =
"qns." + label;
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
bool has_immediate_feedback() const
Whether immediate feedback is EFFECTIVE anywhere in the model.
bool has_priorities() const
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
std::vector< NodeDef > nodes
every node, in creation order
bool has_product_form() const
std::vector< std::size_t > station_to_node
(nstations) 1-based node index
static void step(const char *fmt,...)
Write one progress line.
The exception types the port throws.
The language-feature gate: what a MODEL uses against what a SOLVER declares.
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").
lqn::LqnModel< T > qn2lqn(const qn::NetworkStruct< T > &sn)
Port of MATLAB QN2LQN(model), over the refreshed C++ NetworkStruct.
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).
LqnElement
LQN element kinds, with the values of MATLAB LayeredNetworkElement.
NodeType
Node kinds, with the values of MATLAB NodeType.
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.
void check_qnsolver_supported(const qn::NetworkStruct< T > &L)
The gate runAnalyzer.m reaches through runAnalyzerChecks, narrowed to what the JMVA document can actu...
std::vector< std::string > qns_methods()
Port of SolverLQNS.listValidMethods on a flat Network.
bool is_qnsolver_multiserver(const std::string &m)
The approximations qnsolver -m accepts; the rest reach it only via LQNS.
std::string qns_multiserver(const std::string &method)
The multiserver approximation a Network method selects: the part after qns.
std::string qns_method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
SolverLQNS.supportsModelMethod's structural rules on a Network, as the REASON they refuse,...
std::string resolve_multiserver(const QnsOptions &opt)
Port of the method -> options.config.multiserver map of runAnalyzerNetwork.m.
bool is_qns_method(const std::string &method)
True for qns and every qns.NAME.
void check_qns_method(const std::string &method)
Port of runAnalyzerNetwork's method gate.
bool qnsolver_is_available()
Port of SolverLQNS.hasQnsolver: a native qnsolver binary on the PATH.
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.
Matrix< T > filter_metric(const qn::NetworkStruct< T > &L, const Matrix< T > &metric, MetricKind kind, const std::vector< std::vector< bool > > *zero_mask)
Port of filterMetric: what @@NetworkSolver/getAvg does between the analyzer and the caller.
ClassResults< T > sn_deaggregate_chain_results(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const Matrix< T > &Qchain, const Matrix< T > &Uchain, const Matrix< T > &Rchain, const Matrix< T > &Tchain, const std::vector< T > &Xchain)
Port of sn_deaggregate_chain_results.
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
void check_binding_capacity(const std::string &solver, const NetworkStruct< T > &sn)
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
Convert a closed queueing network into the layered model used by SolverLQNS.
Chain aggregation and de-aggregation.
SolverLQNS: the layered model solved by the external lqns / lqsim binaries.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
The intermediate model, and the second stage that flattens it.
SolverLQNS.defaultOptions on a flat Network plus the knobs the JMVA document carries.
std::size_t samples
options.samples, the JMVA maxSamples
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 metrics getAvg returns, after filtering.
std::string method
the method asked for
std::string actualmethod
the algorithm that ran
Outcome of a captured command.
int exitCode
Exit status, or -1 when the command could not run.
Running an external command and capturing its output, with a deadline.
A scratch directory for the subprocess wrappers, the port's lineTempName.