LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ldes_cli.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5
6/**
7 * @file
8 * `ldes`: the native LDES engine behind the interface of `jline.cli.LdesCLI`.
9 *
10 * WHAT THIS BINARY IS FOR. `common/ldes` has been a GraalVM image of the JAVA
11 * engine; this program is its replacement, built from
12 * `cpp/include/line/solvers/ldes/`. Every client of that binary -- MATLAB's
13 * `@SolverLDES/solveCli.m`, native Python's `wrappers/solver_ldes`, and the C++
14 * client `wrappers/ldes/solver_ldes.h` -- speaks to it through exactly two
15 * artefacts: the ARGUMENT VECTOR and the `ldes-result` DOCUMENT. Neither may
16 * drift. A client is not recompiled when the engine changes hands, so an
17 * argument this program rejects and the Java one accepted, or a key it spells
18 * differently, is a silent breakage in three codebases at once.
19 *
20 * THE INTERFACE IS THEREFORE COPIED, not designed:
21 *
22 * ldes solve <model.json> -o <result.json> [flags]
23 *
24 * with the flag set of `LdesCLI.handleSolveCommand` and the document shape of
25 * `LDESResultIO.write`. Flags this engine cannot honour are REFUSED BY NAME
26 * rather than ignored, which is the one deliberate difference from the AOT
27 * image it replaces: that image ignored flags it predated, and an ignored
28 * `--slotlength` silently ran a continuous-time simulation under a slotted
29 * model's name. A refusal is visible; a silent default is not.
30 *
31 * WHAT IS NOT HERE. `--rest`/server mode belongs to `line_cli` and is refused
32 * with the reason rather than accepted and dropped.
33 */
34
35#include <cmath>
36#include <cstdio>
37#include <ctime>
38#include <cstdlib>
39#include <fstream>
40#include <iostream>
41#include <sstream>
42#include <string>
43#include <vector>
44
48#include "line/util/error.h"
49
50namespace {
51
54
55void print_help() {
56 std::cout
57 << "LDES: discrete-event simulation engine for LINE models.\n\n"
58 << "Usage: ldes solve <model.json> [-o <result.json>] [options]\n\n"
59 << "Options:\n"
60 << " -s, --samples N service completions to simulate (default 200000)\n"
61 << " -e, --maxevents N event budget; overrides --samples\n"
62 << " --maxtime SECONDS wall-clock budget\n"
63 << " --seed N run seed (default 23000; -1 draws one)\n"
64 << " --method NAME engine method (default 'default')\n"
65 << " --cnvgon stop on the convergence check\n"
66 << " --cnvgtol X relative tolerance of that check\n"
67 << " --cnvgbatch N batches before the first check\n"
68 << " --cnvgchk N events between checks\n"
69 << " --tranfilter NAME mser5 | fixed | none\n"
70 << " --mserbatch N MSER batch size\n"
71 << " --warmupfrac X warmup fraction, for --tranfilter fixed\n"
72 << " --cimethod NAME obm | bm | spectral | none\n"
73 << " --obmoverlap X overlap of the batch means\n"
74 << " --ciminbatch N batches below which no CI is reported\n"
75 << " --ciminobs N observations below which no CI is reported\n"
76 << " --spectrallowfreqfrac X low-frequency fraction of the spectral CI\n"
77 << " --slotted run on the slot lattice\n"
78 << " --slotlength X slot length (implies --slotted)\n"
79 << " --replications N independent replications\n"
80 << " --numthreads N threads across replications\n"
81 << " --timespan T0,T1 transient run over [T0,T1]\n"
82 << " --busyperiod K busy-period orders 1..K\n"
83 << " --busyperiod-subnet i,j,... an extra busy-period target\n"
84 << " --initsol V initial state, station-major, comma separated\n"
85 << " --export-histogram fill the state histogram block\n"
86 << " --trajectory fill the state trajectory block\n"
87 << " --respt-samples record every per-visit response time\n"
88 << " -h, --help this text\n";
89}
90
91[[noreturn]] void fail(const std::string& msg) {
92 std::cerr << "Error: " << msg << "\n";
93 std::exit(1);
94}
95
96/** A flag the reference accepts and this engine cannot honour: say so. */
97[[noreturn]] void refuse(const std::string& flag, const std::string& why) {
98 std::cerr << "Error: " << flag << " is accepted by the Java engine but not by this one: "
99 << why << ".\nRun the model through common/ldes.jar for it, rather than "
100 "taking a silently different simulation.\n";
101 std::exit(1);
102}
103
104std::string need_value(int argc, char** argv, int& i, const std::string& flag) {
105 if (i + 1 >= argc) fail(flag + " requires a value.");
106 return std::string(argv[++i]);
107}
108
109std::vector<double> parse_doubles(const std::string& csv) {
110 std::vector<double> out;
111 std::stringstream ss(csv);
112 std::string tok;
113 while (std::getline(ss, tok, ',')) {
114 if (!tok.empty()) out.push_back(std::atof(tok.c_str()));
115 }
116 return out;
117}
118
119std::vector<std::size_t> parse_indices(const std::string& csv) {
120 std::vector<std::size_t> out;
121 std::stringstream ss(csv);
122 std::string tok;
123 while (std::getline(ss, tok, ',')) {
124 if (!tok.empty()) out.push_back(static_cast<std::size_t>(std::atol(tok.c_str())));
125 }
126 return out;
127}
128
129/** A JSON number the way the reference writes it: full round-trip precision. */
130std::string num(double v) {
131 if (std::isnan(v)) return "null";
132 if (std::isinf(v)) return v > 0 ? "1e999" : "-1e999";
133 char buf[40];
134 std::snprintf(buf, sizeof(buf), "%.17g", v);
135 return std::string(buf);
136}
137
138/** A (rows x cols) matrix as the nested array `LDESResultIO` emits. */
139std::string mat_json(const line::Matrix<double>& m, std::size_t rows, std::size_t cols) {
140 std::string s = "[";
141 for (std::size_t i = 0; i < rows; ++i) {
142 if (i) s += ", ";
143 s += "[";
144 for (std::size_t j = 0; j < cols; ++j) {
145 if (j) s += ", ";
146 const bool inside = (i < m.rows() && j < m.cols());
147 s += num(inside ? m(i, j) : 0.0);
148 }
149 s += "]";
150 }
151 s += "]";
152 return s;
153}
154
155/** The per-visit response times as the nested `[station][class][sample]` array. */
156std::string respt_samples_json(const LdesResult& r) {
157 if (r.respTimeSamples.empty()) return std::string();
158 std::string s = "[";
159 for (std::size_t i = 0; i < r.respTimeSamples.size(); ++i) {
160 if (i) s += ", ";
161 s += "[";
162 for (std::size_t k = 0; k < r.respTimeSamples[i].size(); ++k) {
163 if (k) s += ", ";
164 s += "[";
165 for (std::size_t j = 0; j < r.respTimeSamples[i][k].size(); ++j) {
166 if (j) s += ", ";
167 s += num(r.respTimeSamples[i][k][j]);
168 }
169 s += "]";
170 }
171 s += "]";
172 }
173 s += "]";
174 return s;
175}
176
177std::string str_json(const std::string& s) {
178 std::string out = "\"";
179 for (char c : s) {
180 if (c == '"' || c == '\\') {
181 out += '\\';
182 out += c;
183 } else if (c == '\n') {
184 out += "\\n";
185 } else {
186 out += c;
187 }
188 }
189 out += "\"";
190 return out;
191}
192
193/**
194 * The `ldes-result` document, key for key with `LDESResultIO.write`.
195 *
196 * The order of the keys is the reference's, and the blocks a run did not fill
197 * are still emitted with their zero matrices: every client indexes this
198 * document by key and by station row, and a missing block reads as a parse
199 * failure rather than as an absent measurement.
200 */
201std::string result_json(const LdesResult& r, const LdesOptions& o, double runtime,
202 std::size_t events) {
203 const std::size_t S = r.nstations, C = r.nclasses;
204 std::string s;
205 s += "{\n";
206 s += " \"format\": \"ldes-result\",\n";
207 s += " \"version\": \"1.0\",\n";
208 s += " \"solver\": \"SolverLDES\",\n";
209 s += " \"method\": " + str_json(o.method) + ",\n";
210 s += " \"runtime\": " + num(runtime) + ",\n";
211 s += " \"converged\": false,\n";
212 s += " \"stoppingReason\": \"max_events\",\n";
213 s += " \"convergenceBatches\": 0,\n";
214 s += " \"totalSimulatedEvents\": " + std::to_string(events) + ",\n";
215
216 s += " \"dimensions\": {\"nstations\": " + std::to_string(S) +
217 ", \"nclasses\": " + std::to_string(C) + ", \"nchains\": " + std::to_string(r.nchains) +
218 ", \"stationNames\": [";
219 for (std::size_t i = 0; i < r.station_names.size(); ++i) {
220 if (i) s += ", ";
221 s += str_json(r.station_names[i]);
222 }
223 s += "], \"classNames\": [";
224 for (std::size_t i = 0; i < r.class_names.size(); ++i) {
225 if (i) s += ", ";
226 s += str_json(r.class_names[i]);
227 }
228 s += "]},\n";
229
230 s += " \"metrics\": {";
231 s += "\"QN\": " + mat_json(r.QN, S, C);
232 s += ", \"UN\": " + mat_json(r.UN, S, C);
233 s += ", \"RN\": " + mat_json(r.RN, S, C);
234 s += ", \"TN\": " + mat_json(r.TN, S, C);
235 s += ", \"AN\": " + mat_json(r.AN, S, C);
236 s += ", \"WN\": " + mat_json(r.WN, S, C);
237 s += ", \"CN\": " + mat_json(r.CN, 1, C);
238 s += ", \"XN\": " + mat_json(r.XN, 1, C);
239 // The sibling-drop rate at a Join, where LossRate = ArvR - Tput does NOT
240 // hold: ArvR counts the SIBLINGS offered and Tput the PARENT jobs released.
241 // `LDESResultIO.write` puts it inside `metrics`, so it goes here and not in
242 // an optional block of its own.
243 if (!r.DropRateJoin.empty()) s += ", \"DropRateJoin\": " + mat_json(r.DropRateJoin, S, C);
244 s += "},\n";
245
246 // THE OPTIONAL BLOCKS, each written ONLY when the run measured it, exactly
247 // as `LDESResultIO.write` gates them. A block emitted empty is not the same
248 // as an absent one: `ldes_prob_from_histogram` and `getAvgBusyPeriod` both
249 // treat presence as the claim that the measurement was taken.
250 // GATED ON THE FLAG, not on the measurement. A transient run computes the
251 // histogram whether or not anyone asked for it, and `LDESResultIO` writes
252 // the block only when the flag was given; emitting it anyway would make
253 // this engine's document differ from the reference's on every transient run.
254 if (o.export_histogram && r.histogram_space.rows() > 0 && r.histogram_time.rows() > 0) {
255 s += " \"stateHistogram\": {";
256 s += "\"space\": " + mat_json(r.histogram_space, r.histogram_space.rows(),
257 r.histogram_space.cols());
258 s += ", \"time\": " + mat_json(r.histogram_time, r.histogram_time.rows(), 1);
259 if (r.traj_space.rows() > 0 && r.traj_time.rows() > 0) {
260 s += ", \"trajSpace\": " +
261 mat_json(r.traj_space, r.traj_space.rows(), r.traj_space.cols());
262 s += ", \"trajTime\": " + mat_json(r.traj_time, r.traj_time.rows(), 1);
263 }
264 s += "},\n";
265 }
266
267 if (!r.busy_periods.empty()) {
268 s += " \"busyPeriods\": {\"orders\": " +
269 std::to_string(r.busy_periods[0].mean.size()) + ", \"targets\": [";
270 for (std::size_t ti = 0; ti < r.busy_periods.size(); ++ti) {
271 const LdesResult::BusyPeriodTarget& t = r.busy_periods[ti];
272 if (ti) s += ", ";
273 s += "{\"name\": " + str_json(t.name) + ", \"stations\": [";
274 for (std::size_t k = 0; k < t.stations.size(); ++k) {
275 if (k) s += ", ";
276 s += std::to_string(t.stations[k]);
277 }
278 s += "], \"class\": " + std::to_string(t.job_class) + ", \"mean\": [";
279 for (std::size_t k = 0; k < t.mean.size(); ++k) {
280 if (k) s += ", ";
281 s += num(t.mean[k]);
282 }
283 s += "], \"count\": [";
284 for (std::size_t k = 0; k < t.count.size(); ++k) {
285 if (k) s += ", ";
286 s += num(t.count[k]);
287 }
288 s += "]}";
289 }
290 s += "]},\n";
291 }
292
293 if (!r.cache_metrics.empty()) {
294 s += " \"cacheMetrics\": {";
295 bool first = true;
296 for (std::map<std::string, line::ldes::LdesCacheMetrics>::const_iterator it =
297 r.cache_metrics.begin();
298 it != r.cache_metrics.end(); ++it) {
299 if (!first) s += ", ";
300 first = false;
301 const line::ldes::LdesCacheMetrics& cm = it->second;
302 s += str_json(it->first) + ": {";
303 s += "\"hit\": " + mat_json(cm.hit, cm.hit.rows(), cm.hit.cols());
304 s += ", \"delayed\": " + mat_json(cm.delayed, cm.delayed.rows(), cm.delayed.cols());
305 s += ", \"miss\": " + mat_json(cm.miss, cm.miss.rows(), cm.miss.cols());
306 s += ", \"latency\": " + mat_json(cm.latency, cm.latency.rows(), cm.latency.cols());
307 s += ", \"hitList\": " + mat_json(cm.hitList, cm.hitList.rows(), cm.hitList.cols());
308 s += ", \"itemProb\": " + mat_json(cm.itemProb, cm.itemProb.rows(), cm.itemProb.cols());
309 s += ", \"listCost\": " + mat_json(cm.listCost, cm.listCost.rows(), cm.listCost.cols());
310 s += "}";
311 }
312 s += "},\n";
313 }
314
315 if (r.QNfcr.rows() > 0) {
316 const std::size_t G = r.QNfcr.rows();
317 s += " \"fcr\": {\"nregions\": " + std::to_string(G);
318 s += ", \"QNfcr\": " + mat_json(r.QNfcr, G, C);
319 s += ", \"UNfcr\": " + mat_json(r.UNfcr, G, C);
320 s += ", \"RNfcr\": " + mat_json(r.RNfcr, G, C);
321 s += ", \"TNfcr\": " + mat_json(r.TNfcr, G, C);
322 s += ", \"ANfcr\": " + mat_json(r.ANfcr, G, C);
323 s += ", \"WNfcr\": " + mat_json(r.WNfcr, G, C);
324 s += ", \"WeightNfcr\": " + mat_json(r.WeightNfcr, G, C);
325 s += ", \"MemOccNfcr\": " + mat_json(r.MemOccNfcr, G, C);
326 s += ", \"DropRateNfcr\": " + mat_json(r.DropRateNfcr, G, C);
327 s += "},\n";
328 }
329
330 // The reference gates the whole transient block on `--trajectory`, not on
331 // the run being transient.
332 // The samples appear at TOP LEVEL under --respt-samples and INSIDE the
333 // transient block under --trajectory. Both spellings are the reference's,
334 // and both are read: the C++ client takes whichever is present, MATLAB's
335 // `sample()` and `sampleSys()` read the nested one and returned nothing
336 // while only the top-level spelling existed.
337 const std::string respt_json = respt_samples_json(r);
338 if (o.export_trajectory && !r.t.empty()) {
339 s += " \"transient\": {\"t\": [";
340 for (std::size_t k = 0; k < r.t.size(); ++k) {
341 if (k) s += ", ";
342 s += num(r.t[k]);
343 }
344 s += "]";
345 const char* keys[3] = {"QNt", "UNt", "TNt"};
346 const std::vector<std::vector<line::Matrix<double>>>* src[3] = {&r.QNt, &r.UNt, &r.TNt};
347 for (int q = 0; q < 3; ++q) {
348 s += std::string(", \"") + keys[q] + "\": [";
349 for (std::size_t i = 0; i < src[q]->size(); ++i) {
350 if (i) s += ", ";
351 s += "[";
352 for (std::size_t k = 0; k < (*src[q])[i].size(); ++k) {
353 if (k) s += ", ";
354 const line::Matrix<double>& mm = (*src[q])[i][k];
355 s += mat_json(mm, mm.rows(), mm.cols());
356 }
357 s += "]";
358 }
359 s += "]";
360 }
361 if (!respt_json.empty()) s += ", \"respTimeSamples\": " + respt_json;
362 s += "},\n";
363 }
364
365 if (o.export_respt && !respt_json.empty())
366 s += " \"respTimeSamples\": " + respt_json + ",\n";
367 s += " \"sampleCounts\": {";
368 s += "\"QNSamples\": " + mat_json(r.QNSamples, S, C);
369 s += ", \"UNSamples\": " + mat_json(r.UNSamples, S, C);
370 s += ", \"RNSamples\": " + mat_json(r.RNSamples, S, C);
371 s += ", \"TNSamples\": " + mat_json(r.TNSamples, S, C);
372 s += "},\n";
373
374 s += " \"confidenceIntervals\": {";
375 s += "\"QNCI\": " + mat_json(r.QNCI, S, C);
376 s += ", \"UNCI\": " + mat_json(r.UNCI, S, C);
377 s += ", \"RNCI\": " + mat_json(r.RNCI, S, C);
378 s += ", \"TNCI\": " + mat_json(r.TNCI, S, C);
379 s += ", \"ANCI\": " + mat_json(r.ANCI, S, C);
380 s += ", \"WNCI\": " + mat_json(r.WNCI, S, C);
381 s += "},\n";
382
383 s += " \"relativePrecision\": {";
384 s += "\"QNRelPrec\": " + mat_json(r.QNRelPrec, S, C);
385 s += ", \"UNRelPrec\": " + mat_json(r.UNRelPrec, S, C);
386 s += ", \"RNRelPrec\": " + mat_json(r.RNRelPrec, S, C);
387 s += ", \"TNRelPrec\": " + mat_json(r.TNRelPrec, S, C);
388 s += "},\n";
389
390 s += " \"impatience\": {";
391 s += "\"renegedCustomers\": " + mat_json(r.renegedCustomers, S, C);
392 s += ", \"avgRenegingWaitTime\": " + mat_json(r.avgRenegingWaitTime, S, C);
393 s += ", \"renegingRate\": " + mat_json(r.renegingRate, S, C);
394 s += ", \"balkedCustomers\": " + mat_json(r.balkedCustomers, S, C);
395 s += ", \"balkingProbability\": " + mat_json(r.balkingProbability, S, C);
396 s += ", \"retriedCustomers\": " + mat_json(r.retriedCustomers, S, C);
397 s += ", \"retrialDropped\": " + mat_json(r.retrialDropped, S, C);
398 s += ", \"avgOrbitSize\": " + mat_json(r.avgOrbitSize, S, C);
399 s += "}\n";
400 s += "}\n";
401 return s;
402}
403
404} // namespace
405
406int main(int argc, char** argv) {
407 if (argc < 2) {
408 print_help();
409 return 1;
410 }
411 const std::string cmd = argv[1];
412 if (cmd == "-h" || cmd == "--help") {
413 print_help();
414 return 0;
415 }
416 if (cmd != "solve") {
417 std::cerr << "Error: unknown command '" << cmd << "'. The only command is 'solve'.\n";
418 return 1;
419 }
420 if (argc < 3) fail("solve requires a model file.");
421
422 const std::string model_path = argv[2];
423 std::string output_path;
424 LdesOptions o;
425 bool have_samples = false, have_events = false;
426
427 for (int i = 3; i < argc; ++i) {
428 const std::string a = argv[i];
429 if (a == "-o") {
430 output_path = need_value(argc, argv, i, a);
431 } else if (a == "-s" || a == "--samples") {
432 o.samples = static_cast<std::size_t>(std::atol(need_value(argc, argv, i, a).c_str()));
433 have_samples = true;
434 } else if (a == "-e" || a == "--maxevents") {
435 o.events = static_cast<std::size_t>(std::atol(need_value(argc, argv, i, a).c_str()));
436 have_events = true;
437 } else if (a == "--maxtime") {
438 o.timeout = std::atof(need_value(argc, argv, i, a).c_str());
439 } else if (a == "--seed") {
440 o.seed = std::atol(need_value(argc, argv, i, a).c_str());
441 } else if (a == "--method") {
442 o.method = need_value(argc, argv, i, a);
443 } else if (a == "--cnvgon") {
444 o.cnvgon = true;
445 } else if (a == "--cnvgtol") {
446 o.cnvgtol = std::atof(need_value(argc, argv, i, a).c_str());
447 } else if (a == "--cnvgbatch") {
448 o.cnvgbatch = std::atoi(need_value(argc, argv, i, a).c_str());
449 } else if (a == "--cnvgchk") {
450 o.cnvgchk = std::atoi(need_value(argc, argv, i, a).c_str());
451 } else if (a == "--tranfilter") {
452 o.tranfilter = need_value(argc, argv, i, a);
453 } else if (a == "--mserbatch") {
454 o.mserbatch = std::atoi(need_value(argc, argv, i, a).c_str());
455 } else if (a == "--warmupfrac") {
456 o.warmupfrac = std::atof(need_value(argc, argv, i, a).c_str());
457 } else if (a == "--cimethod") {
458 o.cimethod = need_value(argc, argv, i, a);
459 } else if (a == "--obmoverlap") {
460 o.obmoverlap = std::atof(need_value(argc, argv, i, a).c_str());
461 } else if (a == "--ciminbatch") {
462 o.ciminbatch = std::atoi(need_value(argc, argv, i, a).c_str());
463 } else if (a == "--ciminobs") {
464 o.ciminobs = std::atoi(need_value(argc, argv, i, a).c_str());
465 } else if (a == "--spectrallowfreqfrac") {
466 o.spectral_low_freq_frac = std::atof(need_value(argc, argv, i, a).c_str());
467 } else if (a == "--slotted") {
468 o.slotted = true;
469 } else if (a == "--slotlength") {
470 o.slot_length = std::atof(need_value(argc, argv, i, a).c_str());
471 o.slotted = true; // the reference lets --slotlength imply --slotted
472 } else if (a == "--replications") {
473 o.replications = std::atoi(need_value(argc, argv, i, a).c_str());
474 } else if (a == "--numthreads") {
475 o.numthreads = std::atoi(need_value(argc, argv, i, a).c_str());
476 } else if (a == "--timespan") {
477 const std::vector<double> t = parse_doubles(need_value(argc, argv, i, a));
478 if (t.size() != 2) fail("--timespan takes T0,T1.");
479 o.has_timespan = true;
480 o.t0 = t[0];
481 o.t1 = t[1];
482 } else if (a == "--busyperiod") {
483 o.busy_period_orders = std::atoi(need_value(argc, argv, i, a).c_str());
484 } else if (a == "--busyperiod-subnet") {
485 o.busy_period_subnets.push_back(parse_indices(need_value(argc, argv, i, a)));
486 } else if (a == "--initsol") {
487 o.init_sol = parse_doubles(need_value(argc, argv, i, a));
488 } else if (a == "--export-histogram") {
489 o.export_histogram = true;
490 } else if (a == "--trajectory") {
491 o.export_trajectory = true;
492 } else if (a == "--respt-samples") {
493 o.export_respt = true;
494 } else if (a == "--rest") {
495 refuse(a, "server mode belongs to line-cli, not to this binary");
496 } else if (a == "-h" || a == "--help") {
497 print_help();
498 return 0;
499 } else {
500 fail("unknown option '" + a + "'.");
501 }
502 }
503 if (have_events && !have_samples) o.samples = o.events;
504
505 try {
508
509 const std::clock_t t_start = std::clock();
510 const LdesResult r = line::ldes::ldes_engine_solve(sn, o);
511 const double runtime = static_cast<double>(std::clock() - t_start) / CLOCKS_PER_SEC;
512
513 const std::string doc = result_json(r, o, runtime, o.samples);
514 if (output_path.empty()) {
515 std::cout << doc;
516 } else {
517 std::ofstream out(output_path.c_str());
518 if (!out) fail("cannot write '" + output_path + "'.");
519 out << doc;
520 }
521 std::cerr << "LDES analysis [method: " << o.method
522 << "; type: approximate, randomized; lang: cpp] completed in " << runtime
523 << "s. Iterations: " << o.samples << ".\n";
524 return 0;
525 } catch (const line::UnsupportedError& e) {
526 std::cerr << "Error: " << e.what() << "\n";
527 return 2;
528 } catch (const std::exception& e) {
529 std::cerr << "Error: " << e.what() << "\n";
530 return 1;
531 }
532}
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
Requested feature or arithmetic mode is not ported yet.
Definition error.h:49
A network plus its refreshed NetworkStruct.
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
The exception types the port throws.
int main(int argc, char **argv)
Definition ldes_cli.cpp:406
The NATIVE LDES discrete-event engine.
The option and result records of SolverLDES, the discrete-event simulator.
qn::Network< T > read_network_json(const std::string &path)
Parse a model.json file into a qn::Network<T>.
LdesResult ldes_engine_solve(const qn::NetworkStruct< T > &sn, const LdesOptions &o)
Simulate sn, over independent REPLICATIONS when options.replications > 1.
Reader for the LINE model.json interchange (a Network model) into a qn::Network<T> built through the ...
Per-cache hit/miss/latency, as the cacheMetrics block carries them.
Matrix< double > listCost
(1 x nlists) mean storage cost held per list
Matrix< double > latency
(1 x nclasses) expected retrieval latency
Matrix< double > hitList
(nclasses x nlists) hit probability by list
Matrix< double > itemProb
(nitems x nlists+1) item position law
Matrix< double > delayed
(1 x nclasses) delayed-hit probability
Matrix< double > miss
(1 x nclasses) miss probability
Matrix< double > hit
(1 x nclasses) hit probability
The knobs of one LDES run.
One ldes-result document, parsed.