5#ifndef LINE_SOLVERS_WRAPPERS_QNS_SOLVER_QNS_H
6#define LINE_SOLVERS_WRAPPERS_QNS_SOLVER_QNS_H
82 return {
"default",
"conway",
"rolia",
"zhou",
"suri",
"reiser",
"schmidt"};
87 return m ==
"conway" || m ==
"reiser" || m ==
"rolia" || m ==
"zhou";
107 return "SolverQNS does not support immediate feedback (sn.immfeed): neither the JMVA "
108 "document qnsolver reads nor the LQN QN2LQN writes can keep a self-looping job on "
109 "its server. Use SolverCTMC or SolverSSA, whose state space carries the self-loop.";
113 bool has_multiserver =
false;
114 for (std::size_t i = 0; i < L.
nstations && !has_multiserver; ++i) {
115 const double c = L.
stations[i].nservers;
116 if (std::isfinite(c) && c > 1.0) has_multiserver =
true;
118 if (!has_multiserver)
return std::string();
119 std::string ms = method.empty() ? std::string(
"default") : method;
120 std::transform(ms.begin(), ms.end(), ms.begin(),
121 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
123 return "SolverQNS: the multiserver approximation '" + ms +
124 "' is one LQNS offers and qnsolver does not: 'qnsolver -m' accepts conway, reiser, "
125 "rolia and zhou only; suri and schmidt are available only on the non-product-form "
126 "closed SolverLQNS branch.";
141inline std::vector<std::string> qnsolver_argv() {
142 return {
"env",
"-u",
"LD_LIBRARY_PATH",
"qnsolver"};
155 std::vector<std::string> argv = detail::qnsolver_argv();
156 argv.push_back(
"--help");
164 if (std::find(valid.begin(), valid.end(), method) == valid.end())
165 throw InputError(
"SolverQNS: unknown method '" + method +
166 "'; valid methods are default, conway, rolia, zhou, suri, reiser, "
186 if (
opt.method !=
"default")
return opt.method;
187 if (!
opt.multiserver.empty())
return opt.multiserver;
196 std::vector<double> Q, W, U, Tp;
199inline double parse_double_or_zero(
const std::string& s) {
200 if (s.empty())
return 0.0;
202 const double v = std::strtod(s.c_str(), &end);
203 if (end == s.c_str())
return 0.0;
221inline bool parse_row(
const std::string& line, std::size_t nchains, ParsedRow* out) {
222 if (line.find(
',') == std::string::npos)
return false;
223 if (line.find(
'$') != std::string::npos)
return false;
225 for (std::size_t i = 0; i < line.size(); ++i)
226 if (line[i] !=
' ' && line[i] !=
'\t' && line[i] !=
'\r') s += line[i];
228 std::vector<std::string> parts;
231 const std::size_t j = s.find(
',', b);
232 parts.push_back(s.substr(b, j == std::string::npos ? j : j - b));
233 if (j == std::string::npos)
break;
236 const std::size_t stride = nchains == 1 ? 1 : nchains + 1;
237 if (parts.size() < 1 + 4 * stride)
return false;
239 out->station = parts[0];
240 out->Q.assign(nchains, 0.0);
241 out->W.assign(nchains, 0.0);
242 out->U.assign(nchains, 0.0);
243 out->Tp.assign(nchains, 0.0);
245 for (std::size_t c = 0; c < nchains; ++c) out->Q[c] = parse_double_or_zero(parts[ptr + c]);
247 for (std::size_t c = 0; c < nchains; ++c) out->W[c] = parse_double_or_zero(parts[ptr + c]);
249 for (std::size_t c = 0; c < nchains; ++c) out->U[c] = parse_double_or_zero(parts[ptr + c]);
251 for (std::size_t c = 0; c < nchains; ++c) out->Tp[c] = parse_double_or_zero(parts[ptr + c]);
268 for (std::size_t i = 0; i < L.
nstations; ++i) {
270 if (nt == qn::NodeType::Queue || nt == qn::NodeType::Delay ||
271 nt == qn::NodeType::Source)
275 "' is not a Queue, a Delay or a Source, and the JMVA document qnsolver reads "
276 "carries no other station type");
280 "SolverQNS: class priorities are outside the product-form envelope qnsolver "
317 "SolverQNS does not support immediate feedback (sn.immfeed): neither the JMVA "
318 "document qnsolver reads nor the LQN QN2LQN writes can keep a self-looping job on "
319 "its server. Use SolverCTMC or SolverSSA, whose state space carries the self-loop.");
323 bool has_open =
false;
324 for (std::size_t k = 0; k < K; ++k)
325 if (!std::isfinite(L.
classes[k].population)) has_open =
true;
330 "SolverQNS: class priorities are outside the QN2LQN conversion envelope");
335 lo.
samples =
static_cast<double>(
opt.samples);
342 Matrix<T> Q(M, K, zero), U(M, K, zero), R(M, K, zero), Tp(M, K, zero);
344 const auto element = [&](
const std::string& name,
lqn::LqnElement kind) {
345 for (std::size_t idx = 1; idx <=
lsn.nidx; ++idx)
346 if (
lsn.type[idx] == kind &&
lsn.names[idx] == name)
return idx;
347 return std::size_t(0);
349 for (std::size_t i = 0; i < M; ++i) {
352 if (type != qn::NodeType::Queue && type != qn::NodeType::Delay)
continue;
353 for (std::size_t r = 0; r < K; ++r) {
354 const std::string suffix = std::to_string(node + 1) +
"_" +
355 std::to_string(r + 1);
356 const std::size_t a = element(
"Q" + suffix, lqn::LqnElement::ACTIVITY);
357 if (a == 0)
continue;
367 const std::size_t e = element(
"E" + suffix, lqn::LqnElement::ENTRY);
371 const double servers = L.
stations[i].nservers;
372 if (std::isfinite(servers) && servers > 0.0)
392 out.
actualmethod =
opt.method ==
"default" ? (
"default/" + actual) : actual;
407 bool has_multiserver =
false;
408 for (std::size_t i = 0; i < M; ++i) {
409 const double c = L.
stations[i].nservers;
410 if (std::isfinite(c) && c > 1.0) has_multiserver =
true;
421 "SolverQNS: the multiserver approximation '" + ms +
422 "' is one LQNS offers and qnsolver does not: 'qnsolver -m' accepts conway, reiser, "
423 "rolia and zhou only; suri and schmidt are available only on the non-product-form "
424 "closed SolverLQNS branch");
428 "SolverQNS needs the external 'qnsolver' binary on the PATH. It ships with LQNS "
429 "(http://www.sce.carleton.ca/rads/lqns/); LINE distributes no copy and runs none "
430 "from a container image, because that licence forbids redistribution");
434 const std::string model_file = tmp.
file(
"model.jmva");
435 const std::string result_file = tmp.
file(
"result.jmva");
439 std::vector<std::string> argv = detail::qnsolver_argv();
448 argv.push_back(
"-l");
449 argv.push_back(model_file);
450 if (has_multiserver) argv.push_back(
"-m" + ms);
451 argv.push_back(
"-o");
452 argv.push_back(result_file);
457 throw NumericError(
"SolverQNS: qnsolver did not finish within " +
458 std::to_string(
opt.timeout) +
"s and was killed");
460 throw NumericError(
"SolverQNS: qnsolver exited with code " +
462 (pr.
out.empty() ? std::string() : (
"\n" + pr.
out)));
467 Matrix<T> Qchain(M, C, zero), Uchain(M, C, zero), Wchain(M, C, zero), Tchain(M, C, zero);
468 std::ifstream in(result_file.c_str());
473 throw NumericError(
"SolverQNS: qnsolver wrote no result file at '" + result_file +
"'" +
474 (pr.
out.empty() ? std::string() : (
"\nqnsolver said: " + pr.
out)));
476 std::size_t nrows = 0;
477 while (std::getline(in,
line)) {
478 detail::ParsedRow row;
479 if (!detail::parse_row(
line, C, &row))
continue;
481 for (std::size_t i = 0; i < M; ++i)
486 if (idx == M)
continue;
487 for (std::size_t c = 0; c < C; ++c) {
497 "SolverQNS: qnsolver produced no station rows this model recognises; its output "
498 "names no station of the model");
507 std::vector<T> Xchain(C, zero);
508 for (std::size_t c = 0; c < C; ++c) {
510 if (Tchain(rstat - 1, c) > zero) {
511 Xchain[c] = Tchain(rstat - 1, c);
517 for (std::size_t i = 0; i < M; ++i)
518 if (d.
Vchain(i, c) > zero && Tchain(i, c) > zero) {
519 Xchain[c] = T(Tchain(i, c) / d.
Vchain(i, c));
527 for (std::size_t i = 0; i < M; ++i)
528 for (std::size_t c = 0; c < C; ++c)
534 for (std::size_t i = 0; i < M; ++i) {
535 const double c = L.
stations[i].nservers;
536 if (!std::isfinite(c))
continue;
537 for (std::size_t j = 0; j < C; ++j)
549 std::vector<std::vector<bool>> srcmask(M, std::vector<bool>(K,
false));
550 for (std::size_t i = 0; i < M; ++i)
551 if (L.
stations[i].nodetype == qn::NodeType::Source)
552 for (std::size_t k = 0; k < K; ++k) srcmask[i][k] =
true;
566 const std::string ran = has_multiserver ? ms : std::string(
"default");
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
The layered model solved by lqns or lqsim.
const lqn::LqnStruct< T > & get_struct() const
LqnsSolution< T > get_ensemble_avg()
The six measures on the element index space.
A network plus its refreshed NetworkStruct.
bool has_immediate_feedback() const
any(sn.immfeed(:)): whether any (station, class) pair feeds back.
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.
std::string file(const std::string &name) const
A file inside it.
void keep()
Leave the directory in place, for a caller that wants to inspect it.
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.
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)
mva::AvgResult< T > solver_qns_run_analyzer(const qn::NetworkStruct< T > &L, const QnsOptions &opt)
Port of @@SolverQNS/runAnalyzer.m and solver_qns.m.
bool is_qnsolver_multiserver(const std::string &m)
The approximations qnsolver -m accepts; the rest reach it only via LQNS.
std::string resolve_multiserver(const QnsOptions &opt)
Port of the method -> options.config.multiserver map of runAnalyzer.m.
void check_supported(const qn::NetworkStruct< T > &L)
The gate runAnalyzer.m reaches through runAnalyzerChecks, narrowed to what the JMVA document can actu...
std::string method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
SolverQNS.supportsModelMethod's structural rules, as the REASON they refuse, empty when the pair is s...
std::vector< std::string > list_valid_methods()
Port of SolverQNS.listValidMethods.
bool is_available()
Port of SolverQNS.isAvailable: a native qnsolver binary on the PATH.
void check_method(const std::string &method)
Port of runAnalyzer's method gate.
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
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.
Knobs of the wrapper, the subset of SolverOptions that reaches lqns.
bool keep
Keep the working directory, model and result file after the run.
int timeout_seconds
Deadline for the child, in seconds; not positive waits indefinitely.
std::string multiserver
conway | rolia | zhou | suri | reiser | schmidt | default (= rolia).
double samples
lqsim run length, -A; not positive leaves lqsim's own default.
The six measures, on the element index space, with a defined mask each.
std::vector< bool > defined_U
std::vector< bool > defined_Q
std::vector< bool > defined_R
std::vector< bool > defined_T
The metrics getAvg returns, after filtering.
Matrix< T > RN
response time, per visit
Matrix< T > UN
utilization
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
Matrix< T > AN
arrival rate
The chain-level view of a layer, as sn_get_demands_chain returns it.
Matrix< T > Vchain
(M x C) visits
Class-level results, as sn_deaggregate_chain_results returns them.
SolverQNS.defaultOptions plus the two knobs the JMVA document carries.
bool keep
options.keep: leave the scratch directory behind, to inspect what was sent.
std::size_t samples
options.samples, the JMVA maxSamples
std::string multiserver
options.config.multiserver.
int timeout
Seconds before a hung qnsolver is killed; not positive waits forever.
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.
Running an external command and capturing its output, with a deadline.
A scratch directory for the subprocess wrappers, the port's lineTempName.