LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_lqns.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_WRAPPERS_LQNS_SOLVER_LQNS_H
6#define LINE_SOLVERS_WRAPPERS_LQNS_SOLVER_LQNS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * SolverLQNS: the layered model solved by the external lqns / lqsim binaries.
12 *
13 * Port of matlab/src/solvers/wrappers/LQNS/@@SolverLQNS (SolverLQNS.m,
14 * runAnalyzer.m, parseXMLResults.m, getEnsembleAvg.m, runRemoteLQNS.m), of
15 * jline.solvers.wrappers.lqns.SolverLQNS and of the native-Python twin. It is a
16 * WRAPPER, not an engine: nothing here computes a queueing result. The model is
17 * written as .lqnx (lqn_writer.h), a binary is run over it, and the .lqxo it
18 * leaves behind is read back.
19 *
20 * LINE SHIPS NO LQNS BINARY. Its licence is an evaluation agreement that
21 * forbids redistribution, so the binary is one the user installed; a missing
22 * one is reported by name, with the two ways to obtain an answer anyway (an
23 * install, or a host already running the REST service), rather than as a failed
24 * exec.
25 *
26 * WHAT THE COLUMNS MEAN, and why they are not the ones lqns prints. The
27 * reference's getEnsembleAvg permutes them so that a layered result reads the
28 * same whichever solver produced it:
29 *
30 * QLen <- the element's UTILIZATION (a task's utilization IS its
31 * mean number in service)
32 * Util <- its PROCESSOR utilization: lqns and SolverLN both sum it over
33 * the host's servers, so no rescaling applies. Verbatim for hosts,
34 * tasks and activities; for an entry it is aggregated over the
35 * activity graph, which lqns itself reports as 0 in the
36 * activity-graph form
37 * RespT <- its PHASE 1 SERVICE TIME
38 * Tput <- its throughput
39 * ResidT, ArvR: lqns computes neither, so they stay undefined rather than
40 * being filled with a zero that would read as a computed value
41 *
42 * THE .lqxo IS THE ONLY OUTPUT READ. lqns also prints a human-readable report;
43 * parsing that instead would tie the port to a print format that changes
44 * between releases, and it carries fewer digits than the XML.
45 */
46
48#include <algorithm>
49#include <cmath>
50#include <cstdio>
51#include <cstdlib>
52#include <ctime>
53#include <fstream>
54#include <iterator>
55#include <limits>
56#include <map>
57#include <memory>
58#include <string>
59#include <utility>
60#include <vector>
61
62#include <dirent.h>
63#include <sys/stat.h>
64#include <unistd.h>
65
66#include "json.hpp"
70#include "line/util/error.h"
71#include "line/util/http.h"
73#include "line/util/tempdir.h"
74#include "line/util/xml.h"
76
77namespace line {
78namespace lqns {
79
80/** Knobs of the wrapper, the subset of SolverOptions that reaches lqns. */
82 /**
83 * default | lqns | srvn | exactmva | srvn.exactmva | sim | lqsim | lqns.default
84 *
85 * `sim` and `lqsim` run the SIMULATOR and are the only stochastic ones;
86 * `lqns.default` is lqns with no pragma at all, which is a different fixed
87 * point from `default` and not a synonym for it.
88 */
89 std::string method = "default";
90 /** conway | rolia | zhou | suri | reiser | schmidt | default (= rolia). */
91 std::string multiserver = "default";
92 /** lqsim run length, `-A`; not positive leaves lqsim's own default. */
93 double samples = 10000.0;
94 bool verbose = false;
95 /** Keep the working directory, model and result file after the run. */
96 bool keep = false;
97 /** Deadline for the child, in seconds; not positive waits indefinitely. */
99 /** Solve on a host running lqns-rest instead of on this machine. */
100 bool remote = false;
101 std::string remote_url = "http://localhost:8080";
102};
103
104/**
105 * Everything the .lqxo carries, indexed as the struct is.
106 *
107 * The element vectors are (nidx+1) with slot 0 unused and `callwaiting` is
108 * (ncalls+1); an entry lqns did not report stays NaN, which is how the
109 * reference leaves it and is what tells a caller "not reported" apart from
110 * "reported as zero".
111 */
113 std::vector<double> util, phase1util, phase2util;
114 std::vector<double> phase1svct, phase2svct;
115 std::vector<double> tput, procwaiting, procutil;
116 std::vector<double> residvisit; ///< host residence of ONE execution of an activity
117 std::vector<double> residt; ///< ResidT on SolverLN's definition, see compute_residt
118 std::vector<double> callwaiting;
119 int iterations = 0;
120};
121
122/** The six measures, on the element index space, with a defined mask each. */
123template <class T>
125 std::vector<T> QN, UN, RN, TN, AN, WN;
127 int iterations = 0;
128};
129
130namespace detail {
131
132/** NaN as the .lqxo reader uses it: "lqns did not report this". */
133inline double nan_value() { return std::numeric_limits<double>::quiet_NaN(); }
134
135/** An attribute as a double, NaN when absent or unparsable (str2double). */
136inline double attr_num(const xml::Element* e, const char* key) {
137 const std::string s = e->attr(key);
138 if (s.empty()) return nan_value();
139 char* end = nullptr;
140 const double v = std::strtod(s.c_str(), &end);
141 if (end == s.c_str()) return nan_value();
142 return v;
143}
144
145/**
146 * Snap a value within CoarseTol of an exact tenth onto it.
147 *
148 * lqns reports a quantity that is analytically a multiple of 0.1 with a few
149 * digits of iteration noise, and the reference removes that noise in
150 * getAvgTable before tabulating. It belongs to the TABLE and not to getAvg: the
151 * raw fixed point is what a parity comparison should see.
152 */
153inline double snap_to_tenth(double v) {
154 if (!std::isfinite(v)) return v;
155 const double scaled = v * 10.0;
156 const double snapped = std::floor(scaled + 0.5);
157 if (std::fabs(scaled - snapped) < lang::GlobalConstants::CoarseTol * scaled)
158 return snapped / 10.0;
159 return v;
160}
161
162/**
163 * A private working directory, the C++ lineTempName.
164 *
165 * Delegated rather than rolled again here: LINE_WORKSPACE_ROOT has to relocate
166 * the staged .lqnx, since a containerized lqns bind-mounts that root alone and
167 * sees nothing left under TMPDIR.
168 */
169inline std::string make_temp_dir(const std::string& tag) { return util::make_temp_dir(tag); }
170
171/** Remove a directory this wrapper created, with every file it holds. */
172inline void remove_temp_dir(const std::string& dir) {
173 DIR* d = ::opendir(dir.c_str());
174 if (d != nullptr) {
175 for (struct dirent* ent = ::readdir(d); ent != nullptr; ent = ::readdir(d)) {
176 const std::string name(ent->d_name);
177 if (name == "." || name == "..") continue;
178 ::unlink((dir + "/" + name).c_str());
179 }
180 ::closedir(d);
181 }
182 ::rmdir(dir.c_str());
183}
184
185} // namespace detail
186
187/**
188 * Whether lqns or lqsim can serve this layered model under `method`: empty when it can, else the reason.
189 *
190 * The constructs neither binary models are named one by one, since the .lqnx carries setup/delay-off in the LINE
191 * dialect and lqns would otherwise stop at its own parse error ("Unexpected element <setup>") for a modelling limit
192 * of the binary. Mirrors MATLAB lqns_method_refusal / SolverLQNS.unsupportedLNConstructs, the JAR
193 * SolverLQNS.lqnsMethodRefusal and python lqns_method_refusal; the scheduling rule stays with sched_to_lqnx.
194 */
195template <class T>
196std::string lqns_method_refusal(const lqn::LqnModel<T>& m, const std::string& method) {
197 std::vector<std::string> reasons;
198 const auto fmt_g = [](double v) {
199 char buf[64];
200 std::snprintf(buf, sizeof(buf), "%g", v);
201 return std::string(buf);
202 };
203 for (std::size_t t = 0; t < m.tasks.size(); ++t) {
204 const auto& tk = m.tasks[t];
205 if (tk.nitems > 0)
206 reasons.push_back("task '" + tk.name + "' is a CacheTask, and neither lqns nor lqsim models a cache");
207 for (std::size_t e = 0; e < m.entries.size(); ++e)
208 if (m.entries[e].task_slot == t && m.entries[e].cardinality > 0)
209 reasons.push_back("entry '" + m.entries[e].name + "' on task '" + tk.name + "' is an ItemEntry, and "
210 "neither lqns nor lqsim models an item reference stream");
211 const double su = tk.setuptime.disabled ? 0.0 : num_traits<T>::to_double(tk.setuptime.mean);
213 reasons.push_back("task '" + tk.name + "' declares a setup time (" + fmt_g(su) +
214 "), which neither lqns nor lqsim charges");
215 const double off = tk.delayofftime.disabled ? 0.0 : num_traits<T>::to_double(tk.delayofftime.mean);
217 reasons.push_back("task '" + tk.name + "' declares a delay-off time (" + fmt_g(off) +
218 "), which neither lqns nor lqsim charges");
219 // the schema takes think-time on reference tasks only, so write_lqnx drops it (reported in LqnWriteReport)
220 const double tt = tk.thinktime.disabled ? 0.0 : num_traits<T>::to_double(tk.thinktime.mean);
222 reasons.push_back("task '" + tk.name + "' is not a reference task and declares a think time (" +
223 fmt_g(tt) + "), which the LQN XML schema accepts on reference tasks only");
224 }
225 const auto server_rules = [&](const std::string& name, bool pools, bool lld, bool lcd, bool ljd) {
226 if (pools)
227 reasons.push_back("'" + name + "' declares heterogeneous server pools with a class-compatibility graph, "
228 "which neither lqns nor lqsim models: their multiserver is homogeneous");
229 if (lld) reasons.push_back("'" + name + "' declares a load-dependent service rate, which neither lqns nor lqsim models");
230 if (lcd) reasons.push_back("'" + name + "' declares a class-dependent service rate, which neither lqns nor lqsim models");
231 if (ljd) reasons.push_back("'" + name + "' declares a joint-dependent service rate, which neither lqns nor lqsim models");
232 };
233 for (std::size_t p = 0; p < m.procs.size(); ++p) {
234 const auto pl = m.proc_pools.find(p);
235 const auto ll = m.proc_lldscaling.find(p);
236 const auto cd = m.proc_cdscaling.find(p);
237 const auto jd = m.proc_jdscaling.find(p);
238 server_rules(m.procs[p].name, pl != m.proc_pools.end() && !pl->second.empty(),
239 ll != m.proc_lldscaling.end() && !ll->second.empty(),
240 cd != m.proc_cdscaling.end() && static_cast<bool>(cd->second),
241 jd != m.proc_jdscaling.end() && static_cast<bool>(jd->second));
242 }
243 for (std::size_t t = 0; t < m.tasks.size(); ++t) {
244 const auto& tk = m.tasks[t];
245 server_rules(tk.name, !tk.pools.empty(), !tk.lldscaling.empty(), static_cast<bool>(tk.cdscaling), static_cast<bool>(tk.jdscaling));
246 }
247 if (!reasons.empty()) {
248 std::string joined;
249 for (std::size_t i = 0; i < reasons.size(); ++i) joined += (i ? "; " : "") + reasons[i];
250 return "SolverLQNS cannot serve this model: " + joined +
251 ". Use SolverLN (the native layered solver), which models these constructs.";
252 }
253 const bool sim_method = method == "sim" || method == "lqsim";
254 if (!sim_method) {
255 // lqns stops at "The quorum feature is not supported in this version"; lqsim honours the quorum
256 for (std::size_t t = 0; t < m.tasks.size(); ++t)
257 for (const auto& pc : m.tasks[t].precedences)
258 if (pc.pretype == lang::PrecedenceType::PRE_AND && pc.has_quorum && pc.quorum >= 1 &&
259 pc.quorum < pc.preacts.size())
260 return "task '" + m.tasks[t].name + "' declares a quorum AND-join (" + std::to_string(pc.quorum) +
261 " of " + std::to_string(pc.preacts.size()) +
262 "), which lqns does not support. Use method sim (lqsim), SolverLN or SolverLDES.";
263 }
264 if (sim_method) {
265 for (std::size_t p = 0; p < m.procs.size(); ++p)
266 if (m.procs[p].repl > 1.0)
267 return "'" + m.procs[p].name + "' is replicated " + fmt_g(m.procs[p].repl) +
268 " times, which lqsim refuses to simulate. Use an lqns method, SolverLN or SolverLDES, "
269 "which materialise the replicas.";
270 for (std::size_t t = 0; t < m.tasks.size(); ++t)
271 if (m.tasks[t].repl > 1.0)
272 return "'" + m.tasks[t].name + "' is replicated " + fmt_g(m.tasks[t].repl) +
273 " times, which lqsim refuses to simulate. Use an lqns method, SolverLN or SolverLDES, "
274 "which materialise the replicas.";
275 }
276 return std::string();
277}
278
279/**
280 * The layered model solved by lqns or lqsim.
281 *
282 * Constructed over the INTERMEDIATE model (lqn_reader.h), not over the struct,
283 * because the document handed to the binary has to carry the precedence blocks
284 * and reply entries that getStruct flattens away.
285 */
286template <class T>
288public:
290 : model_(model), opt_(options), sn_(lqn::lqn_finalize(model)) {
291 if (!is_available() && !opt_.remote)
292 throw UnsupportedError(ErrorKind{"LQNS"},
293 "SolverLQNS requires the lqns and lqsim commands on the system path.\n"
294 "Obtain them from their authors at: http://www.sce.carleton.ca/rads/lqns/\n"
295 "LINE ships no LQNS binary and does not redistribute one.\n\n"
296 "Alternatively, point LINE at a host that already runs LQNS by setting\n"
297 "LqnsOptions::remote and LqnsOptions::remote_url.");
298 const std::string m = opt_.method.empty() ? std::string("default") : opt_.method;
299 const std::vector<std::string> valid = list_valid_methods();
300 if (std::find(valid.begin(), valid.end(), m) == valid.end())
301 throw InputError("SolverLQNS: '" + m +
302 "' is not a method of this solver; it takes default, lqns, srvn, "
303 "exactmva, srvn.exactmva, sim, lqsim and lqns.default");
304 opt_.method = m;
305 multiserver_pragma(opt_.multiserver); // refuses an unknown policy here, not mid-run
306 // LINE's own sentence before any .lqnx is written, not the binary's parse error
307 const std::string refusal = lqns_method_refusal(model_, opt_.method);
308 if (!refusal.empty()) throw UnsupportedError(ErrorKind{"Structure"}, refusal);
309 }
310
311 /** True when the native lqns command answers on this machine, any release. */
312 static bool has_local_binary() { return !lqns_version().empty(); }
313
314 /** True when a local binary is installed AND is 6.0 or greater. */
315 static bool is_available() { return lqns_is_available(); }
316
317 /** The version banner of the local binary, empty when there is none. */
318 static std::string version() { return lqns_version(); }
319
320 static std::vector<std::string> list_valid_methods() {
321 // the names and their order are this solver's row of the method registry (doc/line-methods.json); a LayeredNetwork
322 // takes every name but the flat-Network qnsolver ones
323 std::vector<std::string> m;
324 for (const std::string& name : ::line::method_registry::methods("LQNS")) {
325 if (name != "qns" && name.rfind("qns.", 0) != 0) {
326 m.push_back(name);
327 }
328 }
329 return m;
330 }
331
332 /** Only the lqsim methods draw random numbers. */
333 static bool is_stochastic_method(const std::string& method) {
334 return method == "sim" || method == "lqsim";
335 }
336
337 /**
338 * The per-layer feature set, as SolverLQNS.supports declares it.
339 *
340 * A layered model reaches lqns as a whole, so this is the set each LAYER
341 * must fall inside: the product-form station kinds and the four service
342 * distributions the LQN schema can carry.
343 */
344 static std::vector<std::string> feature_set() {
345 const char* names[] = {"Sink", "Source", "Queue",
346 "Coxian", "Erlang", "Exp",
347 "HyperExp", "Buffer", "Server",
348 "JobSink", "RandomSource", "ServiceTunnel",
349 "SchedStrategy_PS", "SchedStrategy_FCFS", "ClosedClass"};
350 return std::vector<std::string>(names, names + sizeof(names) / sizeof(names[0]));
351 }
352
353 const lqn::LqnStruct<T>& get_struct() const { return sn_; }
354 const LqnsOptions& options() const { return opt_; }
355 /** Wall-clock seconds of the last run, the reference's `runtime`. */
356 double runtime() const { return runtime_; }
357 int iterations() const { return raw_.iterations; }
358 /** Everything the .lqxo carried, before the column permutation. */
360 run_analyzer_once();
361 return raw_;
362 }
363
364 /**
365 * Run the binary and read its result back.
366 *
367 * Re-running is what the reference does on every getEnsembleAvg call, but
368 * repeating a lqsim run would silently change the answer between two reads
369 * of the same solver object, so the result is computed once and kept.
370 */
372 const std::clock_t t0 = std::clock();
373 const std::string dir = detail::make_temp_dir("lqns");
374 const std::string stem = dir + "/model";
375 const std::string modelfile = stem + ".lqnx";
376 const std::string resultfile = stem + ".lqxo";
377 try {
378 line::util::LineConsole::step("writing the LQN model to %s", modelfile.c_str());
379 const lqn::LqnWriteReport rep = lqn::write_lqnx(model_, modelfile, "LQN");
380 if (opt_.verbose)
381 for (std::size_t i = 0; i < rep.dropped.size(); ++i)
382 std::fprintf(stderr, "SolverLQNS: %s\n", rep.dropped[i].c_str());
383
384 if (opt_.remote) run_remote(modelfile, resultfile);
385 else run_local(modelfile, resultfile);
386
387 line::util::LineConsole::step("parsing the lqns XML results");
388 parse_lqxo(resultfile);
389 } catch (...) {
390 if (!opt_.keep) detail::remove_temp_dir(dir);
391 throw;
392 }
393 if (!opt_.keep) detail::remove_temp_dir(dir);
394 else if (opt_.verbose) std::fprintf(stderr, "SolverLQNS: files kept in %s\n", dir.c_str());
395 runtime_ = double(std::clock() - t0) / double(CLOCKS_PER_SEC);
396 solved_ = true;
397 }
398
399 /**
400 * The six measures on the element index space.
401 *
402 * Port of getEnsembleAvg.m, including the reachable-multiplicity rescaling
403 * of the utilization column.
404 */
406 run_analyzer_once();
407 const std::size_t n = sn_.nidx;
409 s.iterations = raw_.iterations;
410 auto fill = [&](std::vector<T>& v, std::vector<bool>& d, const std::vector<double>& src) {
411 v.assign(n + 1, num_traits<T>::from_int(0));
412 d.assign(n + 1, false);
413 for (std::size_t i = 1; i <= n; ++i) {
414 if (std::isnan(src[i])) continue;
415 v[i] = num_traits<T>::from_double(src[i]);
416 d[i] = true;
417 }
418 };
419 // lqns sums the processor utilization over the host's busy servers; it is
420 // divided by the servers the work can reach (host_reach_mult), the divisor
421 // of SolverLN and SolverLDES, so the three Util columns agree (P150). The
422 // entry rows were aggregated over the activity graph by aggregate_entry_procutil.
423 fill(s.QN, s.defined_Q, raw_.util);
424 fill(s.UN, s.defined_U, raw_.procutil);
425 for (std::size_t i = 1; i <= n; ++i)
426 if (s.defined_U[i]) s.UN[i] = s.UN[i] / num_traits<T>::from_double(sn_.host_reach_mult(i));
427 fill(s.RN, s.defined_R, raw_.phase1svct);
428 fill(s.TN, s.defined_T, raw_.tput);
429 // ResidT is derived from the .lqxo by compute_residt; lqns computes no
430 // arrival rate per element, so that column stays undefined.
431 fill(s.WN, s.defined_W, raw_.residt);
432 s.AN.assign(n + 1, num_traits<T>::from_int(0));
433 s.defined_A.assign(n + 1, false);
434 return s;
435 }
436
437private:
438 void run_analyzer_once() {
439 if (!solved_) run_analyzer();
440 }
441
442 /** The `-Pmultiserver=` argument of a policy name, empty when there is none. */
443 static std::string multiserver_pragma(const std::string& policy) {
444 if (policy.empty() || policy == "none") return std::string();
445 if (policy == "default") return "-Pmultiserver=rolia";
446 if (policy == "conway" || policy == "rolia" || policy == "zhou" || policy == "suri" ||
447 policy == "reiser" || policy == "schmidt")
448 return "-Pmultiserver=" + policy;
449 throw InputError("SolverLQNS: '" + policy +
450 "' is not a multiserver policy of lqns; it takes conway, rolia, zhou, "
451 "suri, reiser, schmidt and default");
452 }
453
454 /**
455 * The command line, as an argv vector.
456 *
457 * NO SHELL SEES THESE ARGUMENTS. The reference builds one string and hands
458 * it to system(); a model path holding a space would be word-split there,
459 * and this port's working directory comes from TMPDIR, which the caller
460 * controls.
461 *
462 * `env -u LD_LIBRARY_PATH`, which the MATLAB wrapper prefixes, is NOT
463 * reproduced: it exists because MATLAB injects its own libstdc++ ahead of
464 * the system one and lqns then fails to find a GLIBCXX symbol. A plain C++
465 * process has no such injection, and stripping the variable here would
466 * instead break a user who set it to reach their own lqns.
467 */
468 std::vector<std::string> build_argv(const std::string& modelfile) const {
469 const bool sim = is_stochastic_method(opt_.method);
470 std::vector<std::string> a;
471 a.push_back(sim ? "lqsim" : "lqns");
472 if (!opt_.verbose) {
473 a.push_back("-a"); // no advisories
474 a.push_back("-w"); // no warnings
475 }
476 // The simulator has no MVA to configure, so the multiserver pragma is
477 // not passed to it, exactly as the reference declines to.
478 if (!sim) {
479 const std::string ms = multiserver_pragma(opt_.multiserver);
480 if (!ms.empty()) a.push_back(ms);
481 }
482 if (opt_.method == "srvn" || opt_.method == "srvn.exactmva") a.push_back("-Playering=srvn");
483 if (opt_.method == "exactmva" || opt_.method == "srvn.exactmva") a.push_back("-Pmva=exact-mva"); // lqns 6.2 rejects "exact"
484 if (sim && opt_.samples > 0) {
485 char buf[32];
486 std::snprintf(buf, sizeof(buf), "%.0f", opt_.samples);
487 a.push_back("-A");
488 a.push_back(buf);
489 }
490 // `lqns.default` is lqns with NO pragma: a model that loses messages
491 // stops the run there, which is the binary's own default and a
492 // different answer from the one the other methods ask for.
493 if (opt_.method != "lqns.default") a.push_back("-Pstop-on-message-loss=false");
494 a.push_back("-x"); // XML result, the .lqxo this wrapper reads
495 a.push_back(modelfile);
496 return a;
497 }
498
499 void run_local(const std::string& modelfile, const std::string& resultfile) const {
500 const std::vector<std::string> argv = build_argv(modelfile);
501 if (opt_.verbose) {
502 std::string cmd;
503 for (std::size_t i = 0; i < argv.size(); ++i) cmd += (i ? " " : "") + argv[i];
504 std::fprintf(stderr, "SolverLQNS command: %s\n", cmd.c_str());
505 }
506 line::util::LineConsole::step("running the lqns binary as a subprocess");
507 const util::ProcResult r = util::capture(argv, opt_.timeout_seconds, true);
508 if (r.timedOut)
509 throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: " + argv[0] + " did not finish within " +
510 std::to_string(opt_.timeout_seconds) + "s and was killed");
511 if (r.exitCode < 0)
512 throw UnsupportedError(ErrorKind{"LQNS"}, "SolverLQNS: could not run '" + argv[0] +
513 "'; LINE ships no LQNS binary, so it must be installed and on "
514 "the system path");
515 if (opt_.verbose && !r.out.empty()) std::fprintf(stderr, "%s", r.out.c_str());
516 // a model lqns rejects leaves no .lqxo; carry its own message instead of the reader's missing-file one
517 struct stat st;
518 if (r.exitCode != 0 || ::stat(resultfile.c_str(), &st) != 0) {
519 std::string out = r.out;
520 while (!out.empty() && (out.back() == '\n' || out.back() == ' ')) out.pop_back();
521 throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: the LQNS binary (exit status " + std::to_string(r.exitCode) +
522 ") produced no results for this model. Its output was:\n" + out);
523 }
524 }
525
526 /**
527 * The lqns-rest protocol: one POST carrying the whole document.
528 *
529 * Twin of runRemoteLQNS.m and of the native-Python `_run_remote_lqns`. The
530 * service answers with the .lqxo text, which is written where the local run
531 * would have left it so that ONE parser serves both paths.
532 */
533 void run_remote(const std::string& modelfile, const std::string& resultfile) const {
534 std::string content;
535 {
536 std::ifstream in(modelfile.c_str());
537 if (!in) throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: cannot read the model it just wrote: " + modelfile);
538 content.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
539 }
540 const bool sim = is_stochastic_method(opt_.method);
541 std::string base = opt_.remote_url;
542 while (!base.empty() && base[base.size() - 1] == '/') base.erase(base.size() - 1);
543 const std::string url = base + (sim ? "/api/v1/solve/lqsim" : "/api/v1/solve/lqns");
544
545 nlohmann::json req;
546 req["model"]["content"] = content;
547 req["model"]["base64"] = false;
548 req["options"]["include_raw_output"] = true;
549 if (sim) {
550 req["options"]["blocks"] = 30;
551 if (opt_.samples > 0) req["options"]["run_time"] = opt_.samples;
552 } else {
553 std::string ms = opt_.multiserver.empty() || opt_.multiserver == "default"
554 ? std::string("rolia")
555 : opt_.multiserver;
556 req["options"]["pragmas"]["multiserver"] = ms;
557 req["options"]["pragmas"]["stop_on_message_loss"] = false;
558 if (opt_.method == "srvn" || opt_.method == "srvn.exactmva")
559 req["options"]["pragmas"]["layering"] = "srvn";
560 if (opt_.method == "exactmva" || opt_.method == "srvn.exactmva")
561 req["options"]["pragmas"]["mva"] = "exact";
562 }
563
564 const int timeout_ms =
565 (opt_.timeout_seconds > 0 ? opt_.timeout_seconds : 300) * 1000;
566 const http::Response resp = http::post_json(url, req.dump(), timeout_ms);
567 if (resp.status < 200 || resp.status >= 300)
568 throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: remote LQNS at " + url + " returned HTTP " +
569 std::to_string(resp.status) + ": " + resp.body);
570 nlohmann::json out;
571 try {
572 out = nlohmann::json::parse(resp.body);
573 } catch (const std::exception& e) {
574 throw ExternalError(ErrorKind{"LQNS"}, std::string("SolverLQNS: remote LQNS returned a body that is not JSON: ") +
575 e.what());
576 }
577 const std::string status = out.value("status", std::string());
578 if (status == "error" || status == "failed")
579 throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: remote solver returned an error: " +
580 out.value("error", std::string("unknown error")));
581 std::string lqxo;
582 if (out.contains("raw_output") && out["raw_output"].is_object())
583 lqxo = out["raw_output"].value("lqxo", std::string());
584 if (lqxo.empty())
585 throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: remote solver did not return an LQXO document");
586 std::ofstream f(resultfile.c_str());
587 if (!f) throw ExternalError(ErrorKind{"LQNS"}, "SolverLQNS: cannot write the remote result to " + resultfile);
588 f << lqxo;
589 }
590
591 /**
592 * Element index of a name OF A GIVEN KIND, 0 when we hold no such element.
593 *
594 * THE KIND IS PART OF THE KEY, and has to be. A LINE-generated layered model
595 * routinely gives a processor, its task and that task's entry the SAME name
596 * (`c0` in the randomLQN corpus, and throughout lqn_ofbiz), and `lqn.names`
597 * holds all three. The reference looks the name up in that flat list with
598 * `findstring`, which returns EVERY match, and then assigns the result to
599 * all of them -- so a processor row ends up carrying its entry's numbers,
600 * whichever element was written last. The .lqxo says which kind it is
601 * describing, in the tag being read, so the ambiguity does not have to exist
602 * here and this port does not reproduce it. On a model whose names are
603 * unique the two agree element for element.
604 */
605 std::size_t index_of(const std::string& name, lang::LqnElement kind) const {
606 const std::map<std::pair<int, std::string>, std::size_t>::const_iterator it =
607 byname_.find(std::make_pair(static_cast<int>(kind), name));
608 return it == byname_.end() ? 0 : it->second;
609 }
610
611 std::size_t call_index_of(const std::string& name) const {
612 const std::map<std::string, std::size_t>::const_iterator it = bycallname_.find(name);
613 return it == bycallname_.end() ? 0 : it->second;
614 }
615
616 void init_name_index() {
617 if (!byname_.empty()) return;
618 // First declaration wins within a kind, as `find(strcmp(...))(1)` would.
619 for (std::size_t i = 1; i <= sn_.nidx; ++i)
620 byname_.insert(std::make_pair(
621 std::make_pair(static_cast<int>(sn_.type[i]), sn_.names[i]), i));
622 for (std::size_t c = 1; c <= sn_.ncalls; ++c)
623 bycallname_.insert(std::make_pair(sn_.callnames[c], c));
624 }
625
626 /**
627 * Read the .lqxo, a port of parseXMLResults.m.
628 *
629 * The walk is processor -> task -> entry -> phase activities, then the task
630 * activity graph. It is keyed on NAMES throughout, so an activity ordering
631 * that differs between the document and the struct cannot mis-assign a row.
632 */
633 void parse_lqxo(const std::string& resultfile) {
634 init_name_index();
635 const std::size_t n = sn_.nidx;
636 const double nan = detail::nan_value();
637 raw_ = LqnsRawAvg();
638 raw_.util.assign(n + 1, nan);
639 raw_.phase1util.assign(n + 1, nan);
640 raw_.phase2util.assign(n + 1, nan);
641 raw_.phase1svct.assign(n + 1, nan);
642 raw_.phase2svct.assign(n + 1, nan);
643 raw_.tput.assign(n + 1, nan);
644 raw_.procwaiting.assign(n + 1, nan);
645 raw_.procutil.assign(n + 1, nan);
646 raw_.residvisit.assign(n + 1, nan);
647 raw_.residt.assign(n + 1, nan);
648 raw_.callwaiting.assign(sn_.ncalls + 1, nan);
649
650 std::unique_ptr<xml::Element> doc;
651 try {
652 doc = xml::parse_file(resultfile);
653 } catch (const Error&) {
654 throw ExternalError(ErrorKind{"LQNS"},
655 "SolverLQNS: no readable result at " + resultfile +
656 "; the binary ran but wrote no .lqxo, which is what it does when it rejects the "
657 "model (run with verbose to see its diagnostics)");
658 }
659
660 for (const xml::Element* sp : doc->by_tag("solver-params")) {
661 const std::vector<const xml::Element*> g = sp->by_tag("result-general");
662 if (!g.empty()) raw_.iterations = static_cast<int>(detail::attr_num(g[0], "iterations"));
663 }
664
665 for (const xml::Element* pe : doc->by_tag("processor")) {
666 const std::size_t pidx = index_of(pe->attr("name"), lang::LqnElement::HOST);
667 const std::vector<const xml::Element*> pr = pe->by_tag("result-processor");
668 if (pidx && !pr.empty()) raw_.procutil[pidx] = detail::attr_num(pr[0], "utilization");
669
670 for (const xml::Element* te : pe->by_tag("task")) {
671 const std::size_t tidx = index_of(te->attr("name"), lang::LqnElement::TASK);
672 const std::vector<const xml::Element*> tr = te->by_tag("result-task");
673 double task_tput = nan;
674 if (!tr.empty()) {
675 task_tput = detail::attr_num(tr[0], "throughput");
676 if (tidx) {
677 raw_.util[tidx] = detail::attr_num(tr[0], "utilization");
678 raw_.phase1util[tidx] = detail::attr_num(tr[0], "phase1-utilization");
679 raw_.phase2util[tidx] = detail::attr_num(tr[0], "phase2-utilization");
680 raw_.tput[tidx] = task_tput;
681 raw_.procutil[tidx] = detail::attr_num(tr[0], "proc-utilization");
682 }
683 }
684
685 for (const xml::Element* ee : te->by_tag("entry")) {
686 const std::size_t eidx = index_of(ee->attr("name"), lang::LqnElement::ENTRY);
687 const std::vector<const xml::Element*> er = ee->by_tag("result-entry");
688 double entry_tput = nan;
689 if (!er.empty()) {
690 entry_tput = detail::attr_num(er[0], "throughput");
691 if (eidx) {
692 raw_.util[eidx] = detail::attr_num(er[0], "utilization");
693 raw_.phase1util[eidx] = detail::attr_num(er[0], "phase1-utilization");
694 raw_.phase2util[eidx] = detail::attr_num(er[0], "phase2-utilization");
695 raw_.phase1svct[eidx] = detail::attr_num(er[0], "phase1-service-time");
696 raw_.phase2svct[eidx] = detail::attr_num(er[0], "phase2-service-time");
697 raw_.tput[eidx] = entry_tput;
698 raw_.procutil[eidx] = detail::attr_num(er[0], "proc-utilization");
699 }
700 }
701
702 // PH1PH2 form: the phase activities carry their own results
703 const std::vector<const xml::Element*> epa =
704 ee->by_tag("entry-phase-activities");
705 if (epa.empty()) continue;
706 for (const xml::Element* ae : epa[0]->by_tag("activity")) {
707 read_activity(ae, entry_tput, true);
708 }
709 }
710
711 const std::vector<const xml::Element*> tal = te->by_tag("task-activities");
712 if (tal.empty()) continue;
713 for (const xml::Element* ae : tal[0]->by_tag("activity")) {
714 // A synch-call of a phase activity is nested inside the same
715 // subtree in some documents; only direct children of
716 // task-activities are the graph's own activities.
717 if (ae->parent != tal[0]) continue;
718 read_activity(ae, task_tput, false);
719 }
720 }
721 }
722
723 aggregate_entry_procutil();
724 aggregate_entry_svct();
725 compute_residt();
726 }
727
728 /**
729 * Residence time, on SolverLN's definition (its residt).
730 *
731 * The time an activity holds its host per cycle of its task: one
732 * execution's host-demand-mean + proc-waiting (no think time, as in
733 * SolverLN) weighted by the executions per task cycle X_a/X_t, and for a task the sum over its
734 * activities. lqns reports no such column, but the .lqxo carries every term
735 * of it. A task lqns never invoked (X_t = 0) holds its host for no time.
736 * Entries and processors have no ResidT in any solver and stay NaN, as does
737 * any row with a term unreported.
738 */
739 void compute_residt() {
740 const std::size_t n = sn_.nidx;
741 for (std::size_t a = 1; a <= sn_.nacts; ++a) {
742 const std::size_t aidx = sn_.ashift + a;
743 if (aidx > n || aidx >= sn_.parent.size()) continue;
744 const std::size_t tidx = sn_.parent[aidx];
745 if (tidx == 0 || tidx > n) continue;
746 const double xa = raw_.tput[aidx];
747 const double xt = raw_.tput[tidx];
748 const double rv = raw_.residvisit[aidx];
749 if (std::isnan(rv) || std::isnan(xa) || std::isnan(xt)) continue;
750 raw_.residt[aidx] = xt > lang::GlobalConstants::FineTol ? xa / xt * rv : 0.0;
751 }
752 for (std::size_t t = 1; t <= sn_.ntasks; ++t) {
753 const std::size_t tidx = sn_.tshift + t;
754 double sum = 0.0;
755 bool any = false;
756 bool complete = true;
757 for (std::size_t a = 1; a <= sn_.nacts; ++a) {
758 const std::size_t aidx = sn_.ashift + a;
759 if (aidx >= sn_.parent.size() || sn_.parent[aidx] != tidx) continue;
760 any = true;
761 if (std::isnan(raw_.residt[aidx])) {
762 complete = false;
763 break;
764 }
765 sum += raw_.residt[aidx];
766 }
767 if (any && complete) raw_.residt[tidx] = sum;
768 }
769 }
770
771 /**
772 * Phase-1 service time of an entry lqns never invoked.
773 *
774 * lqns omits `phase1-service-time` from `result-entry` exactly when the
775 * entry's throughput is zero: nothing was served, so there is no
776 * per-invocation mean to report. LINE then carried a NaN where the table
777 * says an entry HAS a response time and every other solver reports one,
778 * breaking the NaN mask -- see `_kb/06-solver-catalog.md`. The value is
779 * taken from the activity rows, and ONLY where they are unanimous: if every
780 * activity reachable from the entry reports a zero service time then every
781 * aggregation law agrees on zero -- the serial sum, the branch-weighted mean
782 * of an OrFork, the order statistic of an AndFork -- so the derivation does
783 * not depend on which one applies.
784 *
785 * It is deliberately NOT generalised the way `aggregate_entry_procutil` is.
786 * Utilizations add over an activity graph; response times do not. Measured
787 * over the example corpus, the sum over actsof reproduces
788 * `phase1-service-time` on serial chains only and misses it wherever the
789 * graph branches (`lqn_workflows` `Entry`: 12.5667 reported against 8.5667
790 * summed, `lqn_fork_open_arrival` `SE`: 0.841667 against 1.0), so a summed
791 * fallback would answer with a number lqns contradicts. An entry whose
792 * activities are unreported, absent, or not all zero keeps NaN.
793 */
794 void aggregate_entry_svct() {
795 for (std::size_t e = 1; e <= sn_.nentries; ++e) {
796 const std::size_t eidx = sn_.eshift + e;
797 if (eidx >= sn_.actsof.size() || eidx >= raw_.phase1svct.size()) continue;
798 if (!std::isnan(raw_.phase1svct[eidx])) continue;
799 const std::vector<std::size_t>& acts = sn_.actsof[eidx];
800 if (acts.empty()) continue;
801 bool all_zero = true;
802 for (std::size_t aidx : acts) {
803 if (aidx >= raw_.phase1svct.size() || std::isnan(raw_.phase1svct[aidx]) ||
804 raw_.phase1svct[aidx] != 0.0) {
805 all_zero = false;
806 break;
807 }
808 }
809 if (all_zero) raw_.phase1svct[eidx] = 0.0;
810 }
811 }
812
813 /**
814 * Processor utilization of an entry, aggregated from its activity graph.
815 *
816 * lqns credits host work to whichever level carries the host demand. In the
817 * activity-graph form an entry declares none, so lqns reports its
818 * result-entry proc-utilization as a literal 0 and the work sits on the
819 * result-activity rows; the entry's value is then the sum over the
820 * activities reachable from the entry within its own task, which is what
821 * actsof holds. In PH1PH2 form the same sum runs over the phase activities
822 * and reproduces the value lqns reports there, so no form test is needed. An
823 * entry with no activities, or any activity lqns left unreported, keeps the
824 * raw attribute rather than a partial sum.
825 */
826 void aggregate_entry_procutil() {
827 for (std::size_t e = 1; e <= sn_.nentries; ++e) {
828 const std::size_t eidx = sn_.eshift + e;
829 if (eidx >= sn_.actsof.size()) continue;
830 const std::vector<std::size_t>& acts = sn_.actsof[eidx];
831 if (acts.empty()) continue;
832 double sum = 0.0;
833 bool complete = true;
834 for (std::size_t aidx : acts) {
835 if (aidx >= raw_.procutil.size() || std::isnan(raw_.procutil[aidx])) {
836 complete = false;
837 break;
838 }
839 sum += raw_.procutil[aidx];
840 }
841 if (complete) raw_.procutil[eidx] = sum;
842 }
843 }
844
845 /**
846 * One `<activity>` and the calls it issues.
847 *
848 * @param ae the element
849 * @param owner_tput throughput of the entry (phase form) or task, used
850 * where lqns omits the activity's own
851 * @param phase_form true inside entry-phase-activities, where lqns omits
852 * both throughput and proc-utilization
853 */
854 void read_activity(const xml::Element* ae, double owner_tput, bool phase_form) {
855 const std::string aname = ae->attr("name");
856 const std::size_t aidx = index_of(aname, lang::LqnElement::ACTIVITY);
857 const std::vector<const xml::Element*> ar = ae->by_tag("result-activity");
858 if (aidx && !ar.empty()) {
859 const xml::Element* r = ar[0];
860 raw_.util[aidx] = detail::attr_num(r, "utilization");
861 raw_.phase1svct[aidx] = detail::attr_num(r, "service-time");
862 raw_.procwaiting[aidx] = detail::attr_num(r, "proc-waiting");
863 // Host residence of one execution: its demand plus the wait for the
864 // host. proc-waiting is a part of service-time, so one that is
865 // negative or exceeds it is not a measurement: lqns 6.2.31 writes
866 // uninitialised values there for some phase activities (6.96e+252 in
867 // its own 76-distributions model, 1.44e+161 in 94-5101-a2-q2b1) while
868 // service-time stays sane. Such a row reports NaN.
869 const double pw = raw_.procwaiting[aidx];
870 const double st = raw_.phase1svct[aidx];
871 raw_.residvisit[aidx] = (pw < 0 || (!std::isnan(st) && pw > st))
872 ? detail::nan_value()
873 : detail::attr_num(ae, "host-demand-mean") + pw;
874 const double t = detail::attr_num(r, "throughput");
875 // Each phase executes once per entry invocation, so an omitted
876 // throughput there IS the entry's; filling it with zero would say
877 // the activity never runs.
878 raw_.tput[aidx] = std::isnan(t) && phase_form ? owner_tput : t;
879 const double pu = detail::attr_num(r, "proc-utilization");
880 if (!std::isnan(pu)) {
881 raw_.procutil[aidx] = pu;
882 } else if (phase_form) {
883 const double hd = detail::attr_num(ae, "host-demand-mean");
884 if (!std::isnan(owner_tput) && !std::isnan(hd)) raw_.procutil[aidx] = owner_tput * hd;
885 }
886 }
887 if (!aidx) return;
888 read_calls(ae, aname, "synch-call", "=>");
889 read_calls(ae, aname, "asynch-call", "->");
890 }
891
892 void read_calls(const xml::Element* ae, const std::string& aname, const char* tag,
893 const char* arrow) {
894 for (const xml::Element* ce : ae->by_tag(tag)) {
895 const std::size_t cidx = call_index_of(aname + arrow + ce->attr("dest"));
896 const std::vector<const xml::Element*> cr = ce->by_tag("result-call");
897 if (cidx && !cr.empty()) raw_.callwaiting[cidx] = detail::attr_num(cr[0], "waiting");
898 }
899 }
900
901 lqn::LqnModel<T> model_;
902 LqnsOptions opt_;
903 lqn::LqnStruct<T> sn_;
904 LqnsRawAvg raw_;
905 std::map<std::pair<int, std::string>, std::size_t> byname_;
906 std::map<std::string, std::size_t> bycallname_;
907 double runtime_ = 0.0;
908 bool solved_ = false;
909};
910
911} // namespace lqns
912} // namespace line
913
914#endif // LINE_SOLVERS_WRAPPERS_LQNS_SOLVER_LQNS_H
ExternalError(const std::string &what)
Definition error.h:126
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
A refusal: the feature, method or analysis is not covered: LINE:unsupported:<kind>.
Definition error.h:117
UnsupportedError(const std::string &what)
Definition error.h:119
const LqnsOptions & options() const
const lqn::LqnStruct< T > & get_struct() const
void run_analyzer()
Run the binary and read its result back.
static bool is_stochastic_method(const std::string &method)
Only the lqsim methods draw random numbers.
static bool is_available()
True when a local binary is installed AND is 6.0 or greater.
SolverLQNS(const lqn::LqnModel< T > &model, const LqnsOptions &options=LqnsOptions())
static std::string version()
The version banner of the local binary, empty when there is none.
static bool has_local_binary()
True when the native lqns command answers on this machine, any release.
static std::vector< std::string > list_valid_methods()
const LqnsRawAvg & raw_avg()
Everything the .lqxo carried, before the column permutation.
double runtime() const
Wall-clock seconds of the last run, the reference's runtime.
LqnsSolution< T > get_ensemble_avg()
The six measures on the element index space.
static std::vector< std::string > feature_set()
The per-layer feature set, as SolverLQNS.supports declares it.
static void step(const char *fmt,...)
Write one progress line.
The exception types the port throws.
Minimal HTTP/1.1 client, enough to talk to a line-*-rest service.
Running progress log of a LINE solver run (the "solver console").
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
LqnModel -> .lqnx, a port of matlab/src/lang/layered/@LayeredNetwork/writeXML.m.
Is a usable lqns installed on this machine?
The method names of each solver, in listing order.
Response post_json(const std::string &url, const std::string &json, int timeoutMillis)
POST a JSON document.
Definition http.h:360
LqnElement
LQN element kinds, with the values of MATLAB LayeredNetworkElement.
Definition lang_types.h:471
bool in(const std::string &s, std::initializer_list< const char * > set)
std::string name(const Element *el, const std::string &key="name")
LqnWriteReport write_lqnx(const LqnModel< T > &m, const std::string &path, const std::string &model_name=std::string("LQN"), bool use_abstract_names=false, std::string *out_text=NULL)
Write a layered model as a .lqnx document.
Definition lqn_writer.h:220
std::string lqns_method_refusal(const lqn::LqnModel< T > &m, const std::string &method)
Whether lqns or lqsim can serve this layered model under method: empty when it can,...
const std::string & lqns_version()
The version banner of the local lqns, empty when there is none.
Definition lqns_probe.h:49
bool lqns_is_available()
True when lqns is installed AND is a release this port speaks.
Definition lqns_probe.h:69
std::vector< std::string > methods(const std::string &solver)
Every method name the solver can be asked for, in listing order.
std::vector< double > stat(const Matrix< double > &A)
Stationary distribution of a stochastic matrix: the left eigenvector for eigenvalue 1,...
Definition mg1.h:221
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
Definition subprocess.h:82
std::string make_temp_dir(const std::string &prefix, bool mountable=false)
Create a private scratch directory named after its caller.
Definition tempdir.h:84
std::unique_ptr< Element > parse_file(const std::string &path)
Read and parse a file.
Definition xml.h:290
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
static constexpr double FineTol
Definition lang_types.h:765
static constexpr double CoarseTol
Definition lang_types.h:766
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:411
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:415
std::map< std::size_t, std::vector< detail::RawServerPool< T > > > proc_pools
Definition lqn_reader.h:441
std::vector< detail::RawProc > procs
Definition lqn_reader.h:414
std::map< std::size_t, CdScaling< T > > proc_jdscaling
Definition lqn_reader.h:439
std::map< std::size_t, CdScaling< T > > proc_cdscaling
Definition lqn_reader.h:437
std::map< std::size_t, std::vector< T > > proc_lldscaling
Queue-dependent service rates and compatibility pools declared on a HOST, by 0-based processor slot.
Definition lqn_reader.h:436
std::vector< detail::RawEntry< T > > entries
Definition lqn_reader.h:416
What the schema could not carry, one human-readable line per loss.
Definition lqn_writer.h:67
std::vector< std::string > dropped
Definition lqn_writer.h:68
Knobs of the wrapper, the subset of SolverOptions that reaches lqns.
Definition solver_lqns.h:81
bool keep
Keep the working directory, model and result file after the run.
Definition solver_lqns.h:96
std::string method
default | lqns | srvn | exactmva | srvn.exactmva | sim | lqsim | lqns.default
Definition solver_lqns.h:89
int timeout_seconds
Deadline for the child, in seconds; not positive waits indefinitely.
Definition solver_lqns.h:98
bool remote
Solve on a host running lqns-rest instead of on this machine.
std::string multiserver
conway | rolia | zhou | suri | reiser | schmidt | default (= rolia).
Definition solver_lqns.h:91
double samples
lqsim run length, -A; not positive leaves lqsim's own default.
Definition solver_lqns.h:93
Everything the .lqxo carries, indexed as the struct is.
std::vector< double > residvisit
host residence of ONE execution of an activity
std::vector< double > callwaiting
std::vector< double > residt
ResidT on SolverLN's definition, see compute_residt.
std::vector< double > procutil
std::vector< double > phase1util
std::vector< double > util
std::vector< double > phase2util
std::vector< double > phase1svct
std::vector< double > phase2svct
std::vector< double > procwaiting
std::vector< double > tput
The six measures, on the element index space, with a defined mask each.
std::vector< bool > defined_U
std::vector< T > UN
std::vector< bool > defined_W
std::vector< bool > defined_A
std::vector< bool > defined_Q
std::vector< T > RN
std::vector< bool > defined_R
std::vector< T > TN
std::vector< T > QN
std::vector< bool > defined_T
std::vector< T > AN
std::vector< T > WN
std::string attr(const std::string &key) const
Attribute value, or the empty string when absent (org.w3c.dom semantics).
Definition xml.h:63
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 ....